Attendee Questions API Guide What Is It? A customizable questionnaire system that event organizers attach to their events to collect information from attendees during registration. Think of it like Google Forms, but built specifically for events and integrated into the ticket purchase flow. The Two Sides Organizer Side (Building the Form) When an organizer creates an event, they can optionally enable "Attendee Questions." The system automatically creates an empty form with one page. The organizer then builds their questionnaire by: Adding pages - to organize questions into logical sections (e.g., "Personal Info", "Dietary Needs", "Travel Details") Adding fields to pages - choosing from various field types like text, email, phone, dropdowns, checkboxes, ratings, file uploads, etc. Adding options - for choice-based fields (dropdown, radio, checkbox), the organizer adds the available choices Configuring settings - deciding when to show the form (before or after checkout), whether it's required, and if walk-in attendees should also complete it The organizer can reorder pages, fields, and options. They can edit labels, add descriptions, set validation rules (minimum length, date ranges, file size limits), and mark fields as required or optional. Attendee Side (Filling the Form) When someone registers for a published event, they see the questionnaire. The system works page-by-page: View a page - attendee sees one page of questions at a time with navigation showing progress (Page 1 of 3) Fill and save - as the attendee completes a page and moves to the next, their answers are validated and saved automatically. If any answer is invalid (wrong format, too short, invalid selection), the page won't save and they see specific error messages for each problematic field. Submit - when ready, the attendee clicks submit. The system checks ALL pages to ensure every required field is answered and all answers are valid. If everything passes, the submission is finalized. If not, they see a summary showing which pages have problems. The key principle: validate before saving, never store invalid data . This keeps the database clean and gives immediate feedback. The Analytics Side (Viewing Results) Once attendees start submitting, the organizer can view comprehensive analytics: Summary statistics - total submissions, completion rates, average time to complete, submission trends over time, and which pages have the highest drop-off rates Field-by-field breakdown - for each question, see response rates and detailed analysis. For choice fields, see how many people selected each option with percentages. For numeric fields, see averages, ranges, and distributions. For text fields, see common words and length statistics. Spreadsheet view - all responses in a table format, like Google Forms' response spreadsheet, with each row being one submission and each column being one question Export - download all data as CSV or Excel for external analysis How Is It Useful? For Event Organizers Logistics planning: Collect t-shirt sizes to order the right quantities Gather dietary requirements for catering Know arrival times for transportation planning Get emergency contact information for safety Attendee profiling: Ask about interests for personalized experiences Collect company/role info for networking features Understand how attendees heard about the event for marketing Compliance and safety: Collect required documents (ID uploads) Gather health declarations Get consent for photography/recording Verify age requirements Engagement: Ask what sessions attendees plan to attend Collect questions for Q&A panels Gather song requests for performers Get feedback on previous events For Attendees Smoother experience: Complete registration in one flow instead of separate forms Save progress and return later (draft mode) Clear validation messages prevent submission errors Know exactly what's required vs optional Personalization: Their preferences are captured for a tailored experience Dietary needs are communicated without awkward conversations Special requirements are noted in advance Real-World Example Music Festival Scenario: An organizer creates a 3-day festival. They enable attendee questions with two pages: Page 1: Essential Info Emergency contact name (required, text) Emergency contact phone (required, phone validation) T-shirt size (required, dropdown: S/M/L/XL) Camping or day pass? (required, radio) Page 2: Preferences Dietary restrictions (optional, checkbox: vegetarian, vegan, gluten-free, halal, kosher) Other dietary notes (optional, textarea) Which headliner are you most excited for? (optional, dropdown with artist names) When 500 people register, the organizer sees: 65% chose Medium or Large t-shirts 23% have dietary restrictions (42% of those are vegetarian) Artist X is the most anticipated headliner with 45% of votes Page 2 has 15% drop-off (people skipping optional questions) They export the data to: Order t-shirts: 50 S, 180 M, 170 L, 100 XL Tell catering: 115 vegetarian meals, 45 vegan, 60 gluten-free Adjust sound check priority based on headliner popularity Why This Design? Page-by-page saves prevent data loss. If someone fills 3 pages then loses internet, they don't lose everything. Strict validation before saving means the database only contains valid, usable data. No garbage entries to clean up later. Empty-body submit means the final submission is just a validation check, not a data transfer. Everything is already saved from auto-saves. Parallel page validation on submit makes the final check fast even with many pages. Separation of draft and published means organizers can freely edit their form while building the event, but once published and people start responding, the structure is stable. Summary It's a form builder tailored for events. Organizers build custom questionnaires, attendees fill them page-by-page with auto-save and validation, and organizers get rich analytics to plan better events. The technical design prioritizes data integrity, user experience, and actionable insights. System Flow ┌─────────────────────────────────────────────────────────────────────────────┐ │ ORGANIZER FLOW │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ │ 1. Create Event Draft │ │ POST /api/v1/e-events/drafts │ │ ↓ │ │ 2. Enable Attendee Questions │ │ PUT /api/v1/e-events/drafts/{draftId}/attendee-questions │ │ ↓ │ │ 3. Add Pages (optional - default page created automatically) │ │ POST /api/v1/e-events/drafts/{draftId}/attendee-questions/pages │ │ ↓ │ │ 4. Add Fields to Pages │ │ POST /api/v1/e-events/drafts/{draftId}/attendee-questions/pages/{pageId}/fields │ │ ↓ │ │ 5. Add Options to Choice Fields (dropdown/radio/checkbox) │ │ POST /api/v1/e-events/drafts/{draftId}/attendee-questions/fields/{fieldId}/options │ │ ↓ │ │ 6. Publish Event │ │ POST /api/v1/e-events/drafts/{draftId}/publish │ │ │ └─────────────────────────────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────────────────────────┐ │ ATTENDEE FLOW │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ │ 1. View Questionnaire (check if registration open) │ │ GET /api/v1/e-events/{eventId}/attendee-questions │ │ ↓ │ │ 2. Get Each Page │ │ GET /api/v1/e-events/{eventId}/attendee-questions/pages/{pageNumber} │ │ ↓ │ │ 3. Save Page Answers (auto-save per page) │ │ PATCH /api/v1/e-events/{eventId}/attendee-questions/responses/pages/{n}│ │ ↓ │ │ 4. Submit Final Response │ │ POST /api/v1/e-events/{eventId}/attendee-questions/responses/submit │ │ │ └─────────────────────────────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────────────────────────┐ │ ANALYTICS FLOW (Organizer Only) │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ │ GET /api/v1/e-events/{eventId}/attendee-questions/analytics/summary │ │ GET /api/v1/e-events/{eventId}/attendee-questions/analytics/fields │ │ GET /api/v1/e-events/{eventId}/attendee-questions/analytics/fields/{id} │ │ GET /api/v1/e-events/{eventId}/attendee-questions/analytics/responses │ │ GET /api/v1/e-events/{eventId}/attendee-questions/analytics/export/csv │ │ │ └─────────────────────────────────────────────────────────────────────────────┘ Authentication All endpoints require JWT authentication except public event viewing. Header: Authorization: Bearer {jwt_token} PART 1: ORGANIZER ENDPOINTS Base URL /api/v1/e-events/drafts/{draftId}/attendee-questions 1.1 Enable Attendee Questions Creates a new questionnaire for the event draft. Automatically creates an empty form with one default page. Endpoint: PUT /api/v1/e-events/drafts/{draftId}/attendee-questions Request: { "displayTime": "BEFORE_CHECKOUT", "isRequiredOnline": true, "applyToAtDoor": false } Request Fields: Field Type Required Description displayTime enum No When to show form: BEFORE_CHECKOUT or AFTER_CHECKOUT . Default: BEFORE_CHECKOUT isRequiredOnline boolean No Must complete before checkout? Default: false applyToAtDoor boolean No Show to walk-in attendees? Default: false Response (201 Created): { "success": true, "httpStatus": "CREATED", "message": "Attendee questions enabled", "data": { "id": "550e8400-e29b-41d4-a716-446655440000", "eventId": "660e8400-e29b-41d4-a716-446655440001", "formId": "770e8400-e29b-41d4-a716-446655440002", "displayTime": "BEFORE_CHECKOUT", "isRequiredOnline": true, "applyToAtDoor": false, "createdAt": "2025-01-23T10:30:00Z", "updatedAt": "2025-01-23T10:30:00Z" } } Error Responses: Status Message 404 Draft not found 403 You don't have access to this draft 403 Attendee questions already enabled. Use update instead. 1.2 Update Attendee Questions Settings Endpoint: PATCH /api/v1/e-events/drafts/{draftId}/attendee-questions Request: { "displayTime": "AFTER_CHECKOUT", "isRequiredOnline": false } All fields optional - only updates provided fields. Response (200 OK): { "success": true, "httpStatus": "OK", "message": "Settings updated", "data": { "id": "550e8400-e29b-41d4-a716-446655440000", "eventId": "660e8400-e29b-41d4-a716-446655440001", "formId": "770e8400-e29b-41d4-a716-446655440002", "displayTime": "AFTER_CHECKOUT", "isRequiredOnline": false, "applyToAtDoor": false, "createdAt": "2025-01-23T10:30:00Z", "updatedAt": "2025-01-23T10:35:00Z" } } 1.3 Get Attendee Questions (with full form) Endpoint: GET /api/v1/e-events/drafts/{draftId}/attendee-questions Response (200 OK): { "success": true, "httpStatus": "OK", "message": "Attendee questions retrieved", "data": { "id": "550e8400-e29b-41d4-a716-446655440000", "eventId": "660e8400-e29b-41d4-a716-446655440001", "formId": "770e8400-e29b-41d4-a716-446655440002", "displayTime": "BEFORE_CHECKOUT", "isRequiredOnline": true, "applyToAtDoor": false, "createdAt": "2025-01-23T10:30:00Z", "updatedAt": "2025-01-23T10:30:00Z", "form": { "formId": "770e8400-e29b-41d4-a716-446655440002", "title": "Attendee Questions - Music Festival 2025", "description": "Custom questions for event attendees", "pages": [ { "pageId": "880e8400-e29b-41d4-a716-446655440003", "title": "Personal Information", "description": "Basic attendee details", "displayOrder": 1, "fields": [ { "fieldId": "990e8400-e29b-41d4-a716-446655440004", "type": "TEXT", "label": "Emergency Contact Name", "description": "Who should we contact in case of emergency?", "placeholder": "Full name", "displayOrder": 1, "required": true, "validation": { "minLength": 2, "maxLength": 100 }, "options": [] }, { "fieldId": "aa0e8400-e29b-41d4-a716-446655440005", "type": "PHONE", "label": "Emergency Contact Phone", "displayOrder": 2, "required": true, "options": [] }, { "fieldId": "bb0e8400-e29b-41d4-a716-446655440006", "type": "DROPDOWN", "label": "T-Shirt Size", "displayOrder": 3, "required": true, "options": [ {"optionId": "opt1", "label": "Small", "displayOrder": 1}, {"optionId": "opt2", "label": "Medium", "displayOrder": 2}, {"optionId": "opt3", "label": "Large", "displayOrder": 3}, {"optionId": "opt4", "label": "X-Large", "displayOrder": 4} ] } ] }, { "pageId": "cc0e8400-e29b-41d4-a716-446655440007", "title": "Dietary Requirements", "displayOrder": 2, "fields": [ { "fieldId": "dd0e8400-e29b-41d4-a716-446655440008", "type": "CHECKBOX", "label": "Dietary Restrictions", "description": "Select all that apply", "displayOrder": 1, "required": false, "options": [ {"optionId": "opt5", "label": "Vegetarian", "displayOrder": 1}, {"optionId": "opt6", "label": "Vegan", "displayOrder": 2}, {"optionId": "opt7", "label": "Gluten-free", "displayOrder": 3}, {"optionId": "opt8", "label": "Halal", "displayOrder": 4}, {"optionId": "opt9", "label": "Kosher", "displayOrder": 5} ] }, { "fieldId": "ee0e8400-e29b-41d4-a716-446655440009", "type": "TEXTAREA", "label": "Other Dietary Notes", "placeholder": "Any allergies or special requirements?", "displayOrder": 2, "required": false } ] } ] } } } Response when not configured (200 OK): { "success": true, "httpStatus": "OK", "message": "Attendee questions not configured", "data": null } 1.4 Disable Attendee Questions Permanently deletes the form and all configuration. Endpoint: DELETE /api/v1/e-events/drafts/{draftId}/attendee-questions Response (200 OK): { "success": true, "httpStatus": "OK", "message": "Attendee questions disabled" } Pages Management 1.5 Add Page Endpoint: POST /api/v1/e-events/drafts/{draftId}/attendee-questions/pages Request: { "title": "Travel Information", "description": "Help us plan your arrival" } Response (201 Created): { "success": true, "httpStatus": "CREATED", "message": "Page added", "data": { "pageId": "ff0e8400-e29b-41d4-a716-446655440010", "title": "Travel Information", "description": "Help us plan your arrival", "displayOrder": 3 } } 1.6 Update Page Endpoint: PATCH /api/v1/e-events/drafts/{draftId}/attendee-questions/pages/{pageId} Request: { "title": "Travel & Accommodation", "description": "Updated description" } Response (200 OK): { "success": true, "httpStatus": "OK", "message": "Page updated", "data": { "pageId": "ff0e8400-e29b-41d4-a716-446655440010", "title": "Travel & Accommodation", "description": "Updated description", "displayOrder": 3 } } 1.7 Delete Page Endpoint: DELETE /api/v1/e-events/drafts/{draftId}/attendee-questions/pages/{pageId} Response (200 OK): { "success": true, "httpStatus": "OK", "message": "Page deleted" } 1.8 Reorder Pages Endpoint: POST /api/v1/e-events/drafts/{draftId}/attendee-questions/pages/reorder Request: { "orderedIds": [ "cc0e8400-e29b-41d4-a716-446655440007", "880e8400-e29b-41d4-a716-446655440003", "ff0e8400-e29b-41d4-a716-446655440010" ] } Response (200 OK): { "success": true, "httpStatus": "OK", "message": "Pages reordered" } Fields Management 1.9 Add Field Endpoint: POST /api/v1/e-events/drafts/{draftId}/attendee-questions/pages/{pageId}/fields Request (Text Field): { "type": "TEXT", "label": "Company Name", "description": "Your current employer", "placeholder": "Enter company name", "required": false, "validation": { "minLength": 2, "maxLength": 100 } } Request (Dropdown Field): { "type": "DROPDOWN", "label": "How did you hear about us?", "required": true } Request (Rating Field): { "type": "RATING", "label": "How excited are you for this event?", "required": true, "validation": { "min": 1, "max": 5 } } Request (File Upload Field): { "type": "FILE", "label": "Upload ID Document", "description": "Government-issued ID for verification", "required": true, "validation": { "maxSizeMb": 5, "accept": "image/*,.pdf" } } Available Field Types: Type Description Has Options TEXT Single line text No TEXTAREA Multi-line text No EMAIL Email with validation No PHONE Phone number No NUMBER Numeric input No DATE Date picker No TIME Time picker No DATETIME Date and time No DROPDOWN Single select dropdown Yes RADIO Single select radio buttons Yes CHECKBOX Multi-select checkboxes Yes RATING Star rating No SCALE Linear scale (1-10) No SLIDER Slider input No FILE File upload No URL URL with validation No CURRENCY Money input No SECTION_HEADER Display only - section title No PARAGRAPH Display only - text block No Response (201 Created): { "success": true, "httpStatus": "CREATED", "message": "Field added", "data": { "fieldId": "110e8400-e29b-41d4-a716-446655440011", "type": "DROPDOWN", "label": "How did you hear about us?", "displayOrder": 1, "required": true, "options": [] } } 1.10 Update Field Endpoint: PATCH /api/v1/e-events/drafts/{draftId}/attendee-questions/fields/{fieldId} Request: { "label": "How did you discover this event?", "required": false, "description": "Help us improve our marketing" } Response (200 OK): { "success": true, "httpStatus": "OK", "message": "Field updated", "data": { "fieldId": "110e8400-e29b-41d4-a716-446655440011", "type": "DROPDOWN", "label": "How did you discover this event?", "description": "Help us improve our marketing", "displayOrder": 1, "required": false, "options": [] } } 1.11 Delete Field Endpoint: DELETE /api/v1/e-events/drafts/{draftId}/attendee-questions/fields/{fieldId} Response (200 OK): { "success": true, "httpStatus": "OK", "message": "Field deleted" } 1.12 Reorder Fields Endpoint: POST /api/v1/e-events/drafts/{draftId}/attendee-questions/pages/{pageId}/fields/reorder Request: { "orderedIds": [ "bb0e8400-e29b-41d4-a716-446655440006", "990e8400-e29b-41d4-a716-446655440004", "aa0e8400-e29b-41d4-a716-446655440005" ] } Response (200 OK): { "success": true, "httpStatus": "OK", "message": "Fields reordered" } Options Management (for DROPDOWN, RADIO, CHECKBOX) 1.13 Add Option Endpoint: POST /api/v1/e-events/drafts/{draftId}/attendee-questions/fields/{fieldId}/options Request: { "label": "Social Media" } Response (201 Created): { "success": true, "httpStatus": "CREATED", "message": "Option added", "data": { "optionId": "220e8400-e29b-41d4-a716-446655440012", "label": "Social Media", "displayOrder": 1 } } 1.14 Update Option Endpoint: PATCH /api/v1/e-events/drafts/{draftId}/attendee-questions/options/{optionId} Request: { "label": "Social Media (Facebook, Instagram, etc.)" } Response (200 OK): { "success": true, "httpStatus": "OK", "message": "Option updated", "data": { "optionId": "220e8400-e29b-41d4-a716-446655440012", "label": "Social Media (Facebook, Instagram, etc.)", "displayOrder": 1 } } 1.15 Delete Option Endpoint: DELETE /api/v1/e-events/drafts/{draftId}/attendee-questions/options/{optionId} Response (200 OK): { "success": true, "httpStatus": "OK", "message": "Option deleted" } 1.16 Reorder Options Endpoint: POST /api/v1/e-events/drafts/{draftId}/attendee-questions/fields/{fieldId}/options/reorder Request: { "orderedIds": [ "220e8400-e29b-41d4-a716-446655440012", "330e8400-e29b-41d4-a716-446655440013", "440e8400-e29b-41d4-a716-446655440014" ] } Response (200 OK): { "success": true, "httpStatus": "OK", "message": "Options reordered" } PART 2: ATTENDEE ENDPOINTS (Public) Base URL /api/v1/e-events/{eventId}/attendee-questions Note: These endpoints require the event to be PUBLISHED. 2.1 Get Questionnaire (First Page) Endpoint: GET /api/v1/e-events/{eventId}/attendee-questions Response - No questionnaire configured: { "success": true, "httpStatus": "OK", "message": "No questionnaire for this event", "data": { "eventId": "660e8400-e29b-41d4-a716-446655440001", "eventTitle": "Music Festival 2025", "hasForm": false, "registrationOpen": false, "message": "No questionnaire for this event" } } Response - Registration not yet open: { "success": true, "httpStatus": "OK", "message": "Registration opens on 2025-02-01", "data": { "eventId": "660e8400-e29b-41d4-a716-446655440001", "eventTitle": "Music Festival 2025", "hasForm": true, "registrationOpen": false, "message": "Registration opens on 2025-02-01", "registrationOpensAt": "2025-02-01T09:00:00Z", "registrationClosesAt": "2025-02-15T18:00:00Z", "displayTime": "BEFORE_CHECKOUT", "isRequired": true } } Response - Registration closed: { "success": true, "httpStatus": "OK", "message": "Registration closed on 2025-02-15", "data": { "eventId": "660e8400-e29b-41d4-a716-446655440001", "eventTitle": "Music Festival 2025", "hasForm": true, "registrationOpen": false, "message": "Registration closed on 2025-02-15", "registrationOpensAt": "2025-02-01T09:00:00Z", "registrationClosesAt": "2025-02-15T18:00:00Z" } } Response - Registration open (returns page 1): { "success": true, "httpStatus": "OK", "message": "Questionnaire retrieved", "data": { "eventId": "660e8400-e29b-41d4-a716-446655440001", "eventTitle": "Music Festival 2025", "hasForm": true, "registrationOpen": true, "registrationOpensAt": "2025-02-01T09:00:00Z", "registrationClosesAt": "2025-02-15T18:00:00Z", "displayTime": "BEFORE_CHECKOUT", "isRequired": true, "currentPage": 1, "totalPages": 2, "formTitle": "Attendee Questions - Music Festival 2025", "formDescription": "Please complete this questionnaire", "page": { "pageId": "880e8400-e29b-41d4-a716-446655440003", "title": "Personal Information", "description": "Basic attendee details", "displayOrder": 1, "fields": [ { "fieldId": "990e8400-e29b-41d4-a716-446655440004", "type": "TEXT", "label": "Emergency Contact Name", "description": "Who should we contact in case of emergency?", "placeholder": "Full name", "displayOrder": 1, "required": true, "validation": { "minLength": 2, "maxLength": 100 }, "options": [] }, { "fieldId": "aa0e8400-e29b-41d4-a716-446655440005", "type": "PHONE", "label": "Emergency Contact Phone", "displayOrder": 2, "required": true, "options": [] }, { "fieldId": "bb0e8400-e29b-41d4-a716-446655440006", "type": "DROPDOWN", "label": "T-Shirt Size", "displayOrder": 3, "required": true, "options": [ {"optionId": "opt1", "label": "Small", "displayOrder": 1}, {"optionId": "opt2", "label": "Medium", "displayOrder": 2}, {"optionId": "opt3", "label": "Large", "displayOrder": 3}, {"optionId": "opt4", "label": "X-Large", "displayOrder": 4} ] } ] } } } 2.2 Get Specific Page Endpoint: GET /api/v1/e-events/{eventId}/attendee-questions/pages/{pageNumber} Example: GET /api/v1/e-events/660e8400-e29b-41d4-a716-446655440001/attendee-questions/pages/2 Response (200 OK): { "success": true, "httpStatus": "OK", "message": "Page 2 of 2", "data": { "eventId": "660e8400-e29b-41d4-a716-446655440001", "eventTitle": "Music Festival 2025", "hasForm": true, "registrationOpen": true, "currentPage": 2, "totalPages": 2, "page": { "pageId": "cc0e8400-e29b-41d4-a716-446655440007", "title": "Dietary Requirements", "displayOrder": 2, "fields": [ { "fieldId": "dd0e8400-e29b-41d4-a716-446655440008", "type": "CHECKBOX", "label": "Dietary Restrictions", "description": "Select all that apply", "displayOrder": 1, "required": false, "options": [ {"optionId": "opt5", "label": "Vegetarian", "displayOrder": 1}, {"optionId": "opt6", "label": "Vegan", "displayOrder": 2}, {"optionId": "opt7", "label": "Gluten-free", "displayOrder": 3}, {"optionId": "opt8", "label": "Halal", "displayOrder": 4}, {"optionId": "opt9", "label": "Kosher", "displayOrder": 5} ] }, { "fieldId": "ee0e8400-e29b-41d4-a716-446655440009", "type": "TEXTAREA", "label": "Other Dietary Notes", "placeholder": "Any allergies or special requirements?", "displayOrder": 2, "required": false } ] } } } Error - Page not found: { "success": false, "httpStatus": "NOT_FOUND", "message": "Page not found. Valid pages: 1 to 2" } 2.3 Save Page Answers (Auto-Save) Saves answers for a single page. Validates ALL fields on the page before saving. If any field is invalid, nothing is saved. Endpoint: PATCH /api/v1/e-events/{eventId}/attendee-questions/responses/pages/{pageNumber} Request: { "answers": { "990e8400-e29b-41d4-a716-446655440004": { "fieldId": "990e8400-e29b-41d4-a716-446655440004", "value": "John Doe" }, "aa0e8400-e29b-41d4-a716-446655440005": { "fieldId": "aa0e8400-e29b-41d4-a716-446655440005", "value": "+255712345678" }, "bb0e8400-e29b-41d4-a716-446655440006": { "fieldId": "bb0e8400-e29b-41d4-a716-446655440006", "value": "Large" } } } Answer Format by Field Type: Field Type Value Format Example TEXT, TEXTAREA, EMAIL, PHONE, URL string "John Doe" NUMBER, CURRENCY, RATING, SCALE, SLIDER number 42 or 4.5 DATE string (YYYY-MM-DD) "2025-02-15" TIME string (HH:MM) "14:30" DATETIME string (ISO) "2025-02-15T14:30:00" DROPDOWN, RADIO string (selected label) "Large" CHECKBOX array of strings ["Vegetarian", "Gluten-free"] FILE object See below File Upload Answer: { "fieldId": "file-field-id", "value": null, "fileUrl": "https://storage.example.com/uploads/id-doc.pdf", "fileName": "id-doc.pdf", "fileSize": 1048576, "fileType": "application/pdf" } Response - Success (200 OK): { "success": true, "httpStatus": "OK", "message": "Page 1 saved", "data": { "eventId": "660e8400-e29b-41d4-a716-446655440001", "pageNumber": 1, "saved": true, "savedAt": "2025-01-23T10:45:00Z", "isValid": true, "errors": [], "totalFieldsOnPage": 3, "answeredFieldsOnPage": 3, "overallProgress": { "totalPages": 2, "completedPages": 1, "totalFields": 5, "answeredFields": 3, "completionPercentage": 60 } } } Response - Validation Failed (400 Bad Request): { "success": false, "httpStatus": "BAD_REQUEST", "message": "Validation failed - page not saved", "data": { "eventId": "660e8400-e29b-41d4-a716-446655440001", "pageNumber": 1, "saved": false, "isValid": false, "errors": [ { "fieldId": "aa0e8400-e29b-41d4-a716-446655440005", "fieldLabel": "Emergency Contact Phone", "errorCode": "INVALID_PHONE", "message": "Please enter a valid phone number" }, { "fieldId": "bb0e8400-e29b-41d4-a716-446655440006", "fieldLabel": "T-Shirt Size", "errorCode": "INVALID_OPTION", "message": "Selected option is not valid" } ], "totalFieldsOnPage": 3, "answeredFieldsOnPage": 3, "overallProgress": { "totalPages": 2, "completedPages": 0, "totalFields": 5, "answeredFields": 0, "completionPercentage": 0 } } } Validation Error Codes: Code Description REQUIRED Required field is missing INVALID_EMAIL Invalid email format INVALID_PHONE Invalid phone number INVALID_NUMBER Not a valid number INVALID_DATE Invalid date format INVALID_TIME Invalid time format INVALID_DATETIME Invalid datetime format INVALID_URL Invalid URL format INVALID_OPTION Selected option not in list INVALID_SELECTION Invalid selection format TOO_SHORT Text too short (minLength) TOO_LONG Text too long (maxLength) BELOW_MIN Number below minimum ABOVE_MAX Number above maximum DATE_TOO_EARLY Date before minDate DATE_TOO_LATE Date after maxDate TOO_FEW_SELECTIONS Not enough checkboxes TOO_MANY_SELECTIONS Too many checkboxes PATTERN_MISMATCH Doesn't match regex FILE_TOO_LARGE File exceeds maxSizeMb INVALID_FILE_TYPE File type not accepted 2.4 Submit Final Response Submits the form. No request body needed - validates all saved pages and submits if all valid. Endpoint: POST /api/v1/e-events/{eventId}/attendee-questions/responses/submit Response - Success (200 OK): { "success": true, "httpStatus": "OK", "message": "Submission successful", "data": { "responseId": "550e8400-e29b-41d4-a716-446655440099", "eventId": "660e8400-e29b-41d4-a716-446655440001", "success": true, "status": "SUBMITTED", "submittedAt": "2025-01-23T10:50:00Z", "pageValidations": [ { "pageNumber": 1, "pageTitle": "Personal Information", "isValid": true, "hasAnswers": true, "totalFields": 3, "answeredFields": 3, "requiredFields": 3, "requiredAnswered": 3, "errors": [] }, { "pageNumber": 2, "pageTitle": "Dietary Requirements", "isValid": true, "hasAnswers": true, "totalFields": 2, "answeredFields": 1, "requiredFields": 0, "requiredAnswered": 0, "errors": [] } ], "totalPages": 2, "validPages": 2, "invalidPages": 0, "allPagesValid": true } } Response - Validation Failed (400 Bad Request): { "success": false, "httpStatus": "BAD_REQUEST", "message": "Validation failed - 1 page(s) have errors", "data": { "eventId": "660e8400-e29b-41d4-a716-446655440001", "success": false, "status": "DRAFT", "pageValidations": [ { "pageNumber": 1, "pageTitle": "Personal Information", "isValid": false, "hasAnswers": true, "totalFields": 3, "answeredFields": 2, "requiredFields": 3, "requiredAnswered": 2, "errors": [ { "fieldId": "bb0e8400-e29b-41d4-a716-446655440006", "fieldLabel": "T-Shirt Size", "errorCode": "REQUIRED", "message": "T-Shirt Size is required" } ] }, { "pageNumber": 2, "pageTitle": "Dietary Requirements", "isValid": true, "hasAnswers": false, "totalFields": 2, "answeredFields": 0, "requiredFields": 0, "requiredAnswered": 0, "errors": [] } ], "totalPages": 2, "validPages": 1, "invalidPages": 1, "allPagesValid": false } } Error - No draft found: { "success": false, "httpStatus": "NOT_FOUND", "message": "No draft found. Please save your answers first." } 2.5 Get Submission Status Endpoint: GET /api/v1/e-events/{eventId}/attendee-questions/responses/status Response - No submission started: { "success": true, "httpStatus": "OK", "message": "No submission started", "data": { "eventId": "660e8400-e29b-41d4-a716-446655440001", "success": false, "status": null, "totalPages": 2, "validPages": 0, "invalidPages": 0, "allPagesValid": false } } Response - Draft in progress: { "success": true, "httpStatus": "OK", "message": "Status: DRAFT", "data": { "responseId": "550e8400-e29b-41d4-a716-446655440099", "eventId": "660e8400-e29b-41d4-a716-446655440001", "success": false, "status": "DRAFT", "submittedAt": null, "pageValidations": [ { "pageNumber": 1, "pageTitle": "Personal Information", "isValid": true, "hasAnswers": true, "totalFields": 3, "answeredFields": 3, "requiredFields": 3, "requiredAnswered": 3, "errors": [] }, { "pageNumber": 2, "pageTitle": "Dietary Requirements", "isValid": true, "hasAnswers": false, "totalFields": 2, "answeredFields": 0, "requiredFields": 0, "requiredAnswered": 0, "errors": [] } ], "totalPages": 2, "validPages": 2, "invalidPages": 0, "allPagesValid": true } } Response - Already submitted: { "success": true, "httpStatus": "OK", "message": "Status: SUBMITTED", "data": { "responseId": "550e8400-e29b-41d4-a716-446655440099", "eventId": "660e8400-e29b-41d4-a716-446655440001", "success": true, "status": "SUBMITTED", "submittedAt": "2025-01-23T10:50:00Z", "totalPages": 2, "validPages": 2, "invalidPages": 0, "allPagesValid": true } } 2.6 Withdraw Submission Endpoint: DELETE /api/v1/e-events/{eventId}/attendee-questions/responses Response (200 OK): { "success": true, "httpStatus": "OK", "message": "Submission withdrawn" } PART 3: ANALYTICS ENDPOINTS (Organizer Only) Base URL /api/v1/e-events/{eventId}/attendee-questions/analytics Note: Only the event organizer can access analytics. 3.1 Get Summary Statistics Endpoint: GET /api/v1/e-events/{eventId}/attendee-questions/analytics/summary Response (200 OK): { "success": true, "httpStatus": "OK", "message": "Summary retrieved", "data": { "eventId": "660e8400-e29b-41d4-a716-446655440001", "eventTitle": "Music Festival 2025", "totalStarted": 250, "totalDrafts": 45, "totalSubmitted": 180, "totalWithdrawn": 25, "completionRate": 72.0, "dropOffRate": 28.0, "averageCompletionTimeSeconds": 185.5, "fastestCompletionSeconds": 45, "slowestCompletionSeconds": 890, "submissionTrend": [ {"date": "2025-01-01", "count": 0}, {"date": "2025-01-02", "count": 5}, {"date": "2025-01-03", "count": 12}, {"date": "2025-01-04", "count": 8}, {"date": "2025-01-05", "count": 15}, {"date": "2025-01-06", "count": 22}, {"date": "2025-01-07", "count": 18} ], "pageDropOff": [ { "pageNumber": 1, "pageTitle": "Personal Information", "started": 250, "completed": 220, "dropOffRate": 12.0 }, { "pageNumber": 2, "pageTitle": "Dietary Requirements", "started": 220, "completed": 180, "dropOffRate": 18.2 } ], "firstSubmissionAt": "2025-01-02T09:15:00", "lastSubmissionAt": "2025-01-23T10:50:00", "generatedAt": "2025-01-23T11:00:00" } } 3.2 Get All Fields Analytics Endpoint: GET /api/v1/e-events/{eventId}/attendee-questions/analytics/fields Response (200 OK): { "success": true, "httpStatus": "OK", "message": "Fields analytics retrieved", "data": { "eventId": "660e8400-e29b-41d4-a716-446655440001", "totalResponses": 180, "fields": [ { "fieldId": "990e8400-e29b-41d4-a716-446655440004", "label": "Emergency Contact Name", "type": "TEXT", "pageNumber": 1, "required": true, "responseCount": 180, "skippedCount": 0, "responseRate": 100.0, "textStats": { "averageLength": 15, "minLength": 5, "maxLength": 45, "commonWords": ["john", "jane", "mary", "james", "david"] } }, { "fieldId": "bb0e8400-e29b-41d4-a716-446655440006", "label": "T-Shirt Size", "type": "DROPDOWN", "pageNumber": 1, "required": true, "responseCount": 180, "skippedCount": 0, "responseRate": 100.0, "optionBreakdown": [ {"value": "Medium", "count": 65, "percentage": 36.1}, {"value": "Large", "count": 58, "percentage": 32.2}, {"value": "Small", "count": 35, "percentage": 19.4}, {"value": "X-Large", "count": 22, "percentage": 12.2} ] }, { "fieldId": "dd0e8400-e29b-41d4-a716-446655440008", "label": "Dietary Restrictions", "type": "CHECKBOX", "pageNumber": 2, "required": false, "responseCount": 95, "skippedCount": 85, "responseRate": 52.8, "optionBreakdown": [ {"value": "Vegetarian", "count": 42, "percentage": 44.2}, {"value": "Gluten-free", "count": 28, "percentage": 29.5}, {"value": "Vegan", "count": 18, "percentage": 18.9}, {"value": "Halal", "count": 12, "percentage": 12.6}, {"value": "Kosher", "count": 5, "percentage": 5.3} ] } ] } } 3.3 Get Single Field Analytics Endpoint: GET /api/v1/e-events/{eventId}/attendee-questions/analytics/fields/{fieldId}?page=0&size=20 Response (200 OK): { "success": true, "httpStatus": "OK", "message": "Field analytics retrieved", "data": { "eventId": "660e8400-e29b-41d4-a716-446655440001", "fieldId": "bb0e8400-e29b-41d4-a716-446655440006", "label": "T-Shirt Size", "type": "DROPDOWN", "required": true, "totalResponses": 180, "responseCount": 180, "skippedCount": 0, "responseRate": 100.0, "optionBreakdown": [ {"value": "Medium", "count": 65, "percentage": 36.1}, {"value": "Large", "count": 58, "percentage": 32.2}, {"value": "Small", "count": 35, "percentage": 19.4}, {"value": "X-Large", "count": 22, "percentage": 12.2} ], "responses": [ { "responseId": "resp-001", "value": "Large", "submittedAt": "2025-01-23T10:50:00" }, { "responseId": "resp-002", "value": "Medium", "submittedAt": "2025-01-23T10:48:00" } ], "totalResponseCount": 180, "page": 0, "pageSize": 20 } } 3.4 Get All Responses (Spreadsheet View) Endpoint: GET /api/v1/e-events/{eventId}/attendee-questions/analytics/responses?page=0&size=20 Response (200 OK): { "success": true, "httpStatus": "OK", "message": "Responses retrieved", "data": { "eventId": "660e8400-e29b-41d4-a716-446655440001", "totalResponses": 180, "page": 0, "pageSize": 20, "totalPages": 9, "columns": [ {"fieldId": "990e8400-e29b-41d4-a716-446655440004", "label": "Emergency Contact Name", "type": "TEXT", "pageNumber": 1}, {"fieldId": "aa0e8400-e29b-41d4-a716-446655440005", "label": "Emergency Contact Phone", "type": "PHONE", "pageNumber": 1}, {"fieldId": "bb0e8400-e29b-41d4-a716-446655440006", "label": "T-Shirt Size", "type": "DROPDOWN", "pageNumber": 1}, {"fieldId": "dd0e8400-e29b-41d4-a716-446655440008", "label": "Dietary Restrictions", "type": "CHECKBOX", "pageNumber": 2}, {"fieldId": "ee0e8400-e29b-41d4-a716-446655440009", "label": "Other Dietary Notes", "type": "TEXTAREA", "pageNumber": 2} ], "responses": [ { "responseId": "resp-001", "submittedById": "user-001", "submittedByName": "kibuti", "submittedByEmail": "kibuti@example.com", "status": "SUBMITTED", "startedAt": "2025-01-23T10:40:00", "submittedAt": "2025-01-23T10:50:00", "completionTimeSeconds": 600, "answers": { "990e8400-e29b-41d4-a716-446655440004": "John Doe", "aa0e8400-e29b-41d4-a716-446655440005": "+255712345678", "bb0e8400-e29b-41d4-a716-446655440006": "Large", "dd0e8400-e29b-41d4-a716-446655440008": ["Vegetarian", "Gluten-free"], "ee0e8400-e29b-41d4-a716-446655440009": "No nuts please" } }, { "responseId": "resp-002", "submittedById": "user-002", "submittedByName": "john_doe", "submittedByEmail": "john@example.com", "status": "SUBMITTED", "startedAt": "2025-01-23T10:30:00", "submittedAt": "2025-01-23T10:45:00", "completionTimeSeconds": 900, "answers": { "990e8400-e29b-41d4-a716-446655440004": "Jane Smith", "aa0e8400-e29b-41d4-a716-446655440005": "+255798765432", "bb0e8400-e29b-41d4-a716-446655440006": "Medium", "dd0e8400-e29b-41d4-a716-446655440008": null, "ee0e8400-e29b-41d4-a716-446655440009": null } } ] } } 3.5 Export to CSV Endpoint: GET /api/v1/e-events/{eventId}/attendee-questions/analytics/export/csv Response: Downloads a CSV file CSV Content: Response ID,Submitted By,Email,Submitted At,Completion Time (seconds),Emergency Contact Name,Emergency Contact Phone,T-Shirt Size,Dietary Restrictions,Other Dietary Notes resp-001,kibuti,kibuti@example.com,2025-01-23T10:50:00,600,John Doe,+255712345678,Large,Vegetarian; Gluten-free,No nuts please resp-002,john_doe,john@example.com,2025-01-23T10:45:00,900,Jane Smith,+255798765432,Medium,, 3.6 Export to Excel Endpoint: GET /api/v1/e-events/{eventId}/attendee-questions/analytics/export/excel Response: Downloads an Excel (.xlsx) file PART 4: COMPLETE ENDPOINTS REFERENCE Organizer Endpoints (Draft Events) Method Endpoint Description PUT /drafts/{draftId}/attendee-questions Enable questionnaire PATCH /drafts/{draftId}/attendee-questions Update settings GET /drafts/{draftId}/attendee-questions Get with full form DELETE /drafts/{draftId}/attendee-questions Disable (delete) POST /drafts/{draftId}/attendee-questions/pages Add page PATCH /drafts/{draftId}/attendee-questions/pages/{pageId} Update page DELETE /drafts/{draftId}/attendee-questions/pages/{pageId} Delete page POST /drafts/{draftId}/attendee-questions/pages/reorder Reorder pages POST /drafts/{draftId}/attendee-questions/pages/{pageId}/fields Add field PATCH /drafts/{draftId}/attendee-questions/fields/{fieldId} Update field DELETE /drafts/{draftId}/attendee-questions/fields/{fieldId} Delete field POST /drafts/{draftId}/attendee-questions/pages/{pageId}/fields/reorder Reorder fields POST /drafts/{draftId}/attendee-questions/fields/{fieldId}/options Add option PATCH /drafts/{draftId}/attendee-questions/options/{optionId} Update option DELETE /drafts/{draftId}/attendee-questions/options/{optionId} Delete option POST /drafts/{draftId}/attendee-questions/fields/{fieldId}/options/reorder Reorder options Attendee Endpoints (Published Events) Method Endpoint Description GET /{eventId}/attendee-questions Get form (page 1) GET /{eventId}/attendee-questions/pages/{n} Get specific page PATCH /{eventId}/attendee-questions/responses/pages/{n} Save page (auto-save) POST /{eventId}/attendee-questions/responses/submit Submit (no body) GET /{eventId}/attendee-questions/responses/status Get my status DELETE /{eventId}/attendee-questions/responses Withdraw Analytics Endpoints (Organizer Only) Method Endpoint Description GET /{eventId}/attendee-questions/analytics/summary Overall stats GET /{eventId}/attendee-questions/analytics/fields All fields breakdown GET /{eventId}/attendee-questions/analytics/fields/{fieldId} Single field detail GET /{eventId}/attendee-questions/analytics/responses Spreadsheet view GET /{eventId}/attendee-questions/analytics/export/csv Download CSV GET /{eventId}/attendee-questions/analytics/export/excel Download Excel PART 5: INSOMNIA TESTING GUIDE Setup Environment Variables base_url: http://localhost:8080/api/v1 jwt_token: draft_id: event_id: page_id: field_id: option_id: Testing Flow Step 1: Create Event Draft POST {{base_url}}/e-events/drafts Authorization: Bearer {{jwt_token}} Content-Type: application/json { "title": "Test Music Festival 2025", "categoryId": "{{category_id}}", "eventFormat": "IN_PERSON", "description": "Test event for questionnaire" } Step 2: Enable Attendee Questions PUT {{base_url}}/e-events/drafts/{{draft_id}}/attendee-questions Authorization: Bearer {{jwt_token}} Content-Type: application/json { "displayTime": "BEFORE_CHECKOUT", "isRequiredOnline": true, "applyToAtDoor": false } Step 3: Get Questionnaire (see default page) GET {{base_url}}/e-events/drafts/{{draft_id}}/attendee-questions Authorization: Bearer {{jwt_token}} Step 4: Add Text Field POST {{base_url}}/e-events/drafts/{{draft_id}}/attendee-questions/pages/{{page_id}}/fields Authorization: Bearer {{jwt_token}} Content-Type: application/json { "type": "TEXT", "label": "Emergency Contact Name", "placeholder": "Full name", "required": true, "validation": { "minLength": 2, "maxLength": 100 } } Step 5: Add Dropdown Field POST {{base_url}}/e-events/drafts/{{draft_id}}/attendee-questions/pages/{{page_id}}/fields Authorization: Bearer {{jwt_token}} Content-Type: application/json { "type": "DROPDOWN", "label": "T-Shirt Size", "required": true } Step 6: Add Options to Dropdown POST {{base_url}}/e-events/drafts/{{draft_id}}/attendee-questions/fields/{{field_id}}/options Authorization: Bearer {{jwt_token}} Content-Type: application/json {"label": "Small"} Repeat for: Medium, Large, X-Large Step 7: Publish Event POST {{base_url}}/e-events/drafts/{{draft_id}}/publish Authorization: Bearer {{jwt_token}} Step 8: (As Attendee) Get Questionnaire GET {{base_url}}/e-events/{{event_id}}/attendee-questions Authorization: Bearer {{attendee_token}} Step 9: (As Attendee) Save Page 1 PATCH {{base_url}}/e-events/{{event_id}}/attendee-questions/responses/pages/1 Authorization: Bearer {{attendee_token}} Content-Type: application/json { "answers": { "{{text_field_id}}": { "fieldId": "{{text_field_id}}", "value": "John Doe" }, "{{dropdown_field_id}}": { "fieldId": "{{dropdown_field_id}}", "value": "Large" } } } Step 10: (As Attendee) Submit POST {{base_url}}/e-events/{{event_id}}/attendee-questions/responses/submit Authorization: Bearer {{attendee_token}} Step 11: (As Organizer) View Analytics GET {{base_url}}/e-events/{{event_id}}/attendee-questions/analytics/summary Authorization: Bearer {{jwt_token}} Step 12: (As Organizer) Export CSV GET {{base_url}}/e-events/{{event_id}}/attendee-questions/analytics/export/csv Authorization: Bearer {{jwt_token}} This guide covers the complete Attendee Questions API. Ready to implement and test!