# 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

```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Operation completed successfully",
  "action_time": "2025-02-17T10:30:45",
  "data": { }
}
```

### Error Response Structure

```json
{
  "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:**
```json
{
  "success": false,
  "httpStatus": "UNAUTHORIZED",
  "message": "Token has expired",
  "action_time": "2025-02-17T10:30:45",
  "data": "Token has expired"
}
```

**403 — Forbidden (not organizer):**
```json
{
  "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):**
```json
{
  "success": false,
  "httpStatus": "FORBIDDEN",
  "message": "Registration closed",
  "action_time": "2025-02-17T10:30:45",
  "data": "Registration closed"
}
```

**404 — Form not enabled:**
```json
{
  "success": false,
  "httpStatus": "NOT_FOUND",
  "message": "Form not enabled",
  "action_time": "2025-02-17T10:30:45",
  "data": "Form not enabled"
}
```

---

## Endpoints

---

## Form Setup

---

## 1. Enable Applicant Form

**Purpose**: Enables the applicant form for an event. Internally creates a Form Builder form titled `"Attendee Questions - {event title}"` with no pages. Pages are added separately by the organizer. Only the event organizer can call this. Returns the empty form structure alongside the config.

**Endpoint**: `POST` `/api/v1/e-events/applicant-form/events/{eventId}/enable`

**Access Level**: 🔒 Protected (Event organizer only)

**Authentication**: Bearer Token

**Request Headers**:

| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <token>` |
| `Content-Type` | string | Yes | `application/json` |

**Path Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `eventId` | UUID | Yes | The event to enable the form for | Must be a valid UUID |

**Request JSON Sample**:

```json
{
  "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**:

```json
{
  "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](#a-applicantformsetupresponse).

**Possible Error Responses**:

| Status | Scenario |
|--------|----------|
| `401` | No or expired token |
| `403` | Not the event organizer, or form is already enabled for this event |
| `404` | Event not found |

---

## 2. Update Form Settings

**Purpose**: Updates the form configuration for the event — `displayTime`, `isRequiredOnline`, and `applyToAtDoor`. Does not affect the form structure (pages/fields). All fields are optional.

**Endpoint**: `PUT` `/api/v1/e-events/applicant-form/events/{eventId}/settings`

**Access Level**: 🔒 Protected (Event organizer only)

**Authentication**: Bearer Token

**Request Headers**:

| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <token>` |
| `Content-Type` | string | Yes | `application/json` |

**Path Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `eventId` | UUID | Yes | The event | Must be a valid UUID |

**Request JSON Sample**:

```json
{
  "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**:

```json
{
  "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](#b-eventapplicantformentity-config-object).

**Possible Error Responses**:

| Status | Scenario |
|--------|----------|
| `401` | No or expired token |
| `403` | Not the event organizer |
| `404` | Event not found or form not enabled |

---

## 3. Disable Form

**Purpose**: Disables and permanently removes the applicant form from the event. The underlying Form Builder form is soft-deleted. Existing submitted responses are preserved. This action cannot be undone — to re-enable a form, call Endpoint 1 again which will create a fresh form.

**Endpoint**: `DELETE` `/api/v1/e-events/applicant-form/events/{eventId}/disable`

**Access Level**: 🔒 Protected (Event organizer only)

**Authentication**: Bearer Token

**Request Headers**:

| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <token>` |

**Path Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `eventId` | UUID | Yes | The event | Must be a valid UUID |

**Success Response JSON Sample**:

```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Applicant form disabled",
  "action_time": "2025-02-17T10:30:45",
  "data": null
}
```

**Possible Error Responses**:

| Status | Scenario |
|--------|----------|
| `401` | No or expired token |
| `403` | Not the event organizer |
| `404` | Event not found or form not enabled |

---

## 4. Get Full Form

**Purpose**: Returns the complete form structure — all pages, fields, and options — in a single call. Intended for the form builder UI to load the current state when the organizer opens the builder. Only the event organizer can call this.

**Endpoint**: `GET` `/api/v1/e-events/applicant-form/events/{eventId}/full-form`

**Access Level**: 🔒 Protected (Event organizer only)

**Authentication**: Bearer Token

**Request Headers**:

| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <token>` |

**Path Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `eventId` | UUID | Yes | The event | Must be a valid UUID |

**Success Response JSON Sample**:

```json
{
  "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](#c-formresponse-full-form-object) with all pages, fields, and options fully nested. Soft-deleted items are excluded. Pages and fields are ordered by `displayOrder`.

**Possible Error Responses**:

| Status | Scenario |
|--------|----------|
| `401` | No or expired token |
| `403` | Not the event organizer |
| `404` | Event not found or form not enabled |

---

## Page Management

---

## 4. Add Page

**Purpose**: Appends a new blank page to the form. `displayOrder` is auto-assigned as the next position. Fields are added to the page separately.

**Endpoint**: `POST` `/api/v1/e-events/applicant-form/events/{eventId}/pages`

**Access Level**: 🔒 Protected (Event organizer only)

**Authentication**: Bearer Token

**Request Headers**:

| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <token>` |
| `Content-Type` | string | Yes | `application/json` |

**Path Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `eventId` | UUID | Yes | The event | Must be a valid UUID |

**Request JSON Sample**:

```json
{
  "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**:

```json
{
  "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](#d-pageresponse).

**Possible Error Responses**:

| Status | Scenario |
|--------|----------|
| `401` | No or expired token |
| `403` | Not the event organizer |
| `404` | Event not found or form not enabled |
| `422` | `title` is blank |

---

## 5. Update Page

**Purpose**: Updates the title, description, or action button label of an existing page.

**Endpoint**: `PUT` `/api/v1/e-events/applicant-form/events/{eventId}/pages/{pageId}`

**Access Level**: 🔒 Protected (Event organizer only)

**Authentication**: Bearer Token

**Request Headers**:

| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <token>` |
| `Content-Type` | string | Yes | `application/json` |

**Path Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `eventId` | UUID | Yes | The event | Must be a valid UUID |
| `pageId` | UUID | Yes | The page to update | Must be a valid UUID |

**Request JSON Sample**:

```json
{
  "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**:

```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Page updated",
  "action_time": "2025-02-17T10:30:45",
  "data": { "...": "PageResponse object" }
}
```

**Success Response Fields**: `data` is a [PageResponse](#d-pageresponse).

**Possible Error Responses**:

| Status | Scenario |
|--------|----------|
| `401` | No or expired token |
| `403` | Not the event organizer |
| `404` | Event not found or page not found |

---

## 6. Delete Page

**Purpose**: Deletes a page. Pass `?hard=false` (default) for soft delete — the page is hidden but historical answer data is preserved. Pass `?hard=true` to permanently remove the page and all its fields.

**Endpoint**: `DELETE` `/api/v1/e-events/applicant-form/events/{eventId}/pages/{pageId}`

**Access Level**: 🔒 Protected (Event organizer only)

**Authentication**: Bearer Token

**Request Headers**:

| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <token>` |

**Path Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `eventId` | UUID | Yes | The event | Must be a valid UUID |
| `pageId` | UUID | Yes | The page to delete | Must be a valid UUID |

**Query Parameters**:

| Parameter | Type | Required | Description | Validation | Default |
|-----------|------|----------|-------------|------------|---------|
| `hard` | boolean | No | `true` = permanent delete, `false` = soft delete | — | `false` |

**Success Response JSON Sample**:

```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Page deleted",
  "action_time": "2025-02-17T10:30:45",
  "data": null
}
```

> When `hard=true`, `message` is `"Page permanently deleted"`.

**Possible Error Responses**:

| Status | Scenario |
|--------|----------|
| `401` | No or expired token |
| `403` | Not the event organizer |
| `404` | Event not found or page not found |

---

## 7. Bulk Add Pages (with Fields)

**Purpose**: Creates multiple pages in one call. Each page can optionally include an inline list of fields.

**Endpoint**: `POST` `/api/v1/e-events/applicant-form/events/{eventId}/pages/bulk`

**Access Level**: 🔒 Protected (Event organizer only)

**Authentication**: Bearer Token

**Request Headers**:

| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <token>` |
| `Content-Type` | string | Yes | `application/json` |

**Path Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `eventId` | UUID | Yes | The event | Must be a valid UUID |

**Request JSON Sample**:

```json
{
  "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**:

```json
{
  "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](#h-bulkoperationresult).

**Possible Error Responses**:

| Status | Scenario |
|--------|----------|
| `401` | No or expired token |
| `403` | Not the event organizer |
| `404` | Event not found or form not enabled |

---

## 8. Bulk Delete Pages

**Purpose**: Deletes multiple pages in one call. Supports both soft delete (default) and hard delete via `?hard` query param. Items are processed individually — a failure on one does not stop the rest.

**Endpoint**: `DELETE` `/api/v1/e-events/applicant-form/events/{eventId}/pages/bulk`

**Access Level**: 🔒 Protected (Event organizer only)

**Authentication**: Bearer Token

**Request Headers**:

| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <token>` |
| `Content-Type` | string | Yes | `application/json` |

**Path Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `eventId` | UUID | Yes | The event | Must be a valid UUID |

**Query Parameters**:

| Parameter | Type | Required | Description | Validation | Default |
|-----------|------|----------|-------------|------------|---------|
| `hard` | boolean | No | `true` = permanent delete | — | `false` |

**Request JSON Sample**:

```json
{
  "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**:

```json
{
  "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](#i-bulkdeleteresult).

**Possible Error Responses**:

| Status | Scenario |
|--------|----------|
| `401` | No or expired token |
| `403` | Not the event organizer |
| `404` | Event not found. Individual page errors reported in `data.errors[]` |

---

## 9. Clone Page

**Purpose**: Creates an exact copy of an existing page including all its active fields and options. The clone is appended at the end of the form with "(Copy)" added to the title.

**Endpoint**: `POST` `/api/v1/e-events/applicant-form/events/{eventId}/pages/{pageId}/clone`

**Access Level**: 🔒 Protected (Event organizer only)

**Authentication**: Bearer Token

**Request Headers**:

| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <token>` |

**Path Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `eventId` | UUID | Yes | The event | Must be a valid UUID |
| `pageId` | UUID | Yes | The page to clone | Must be a valid UUID |

**Success Response JSON Sample**:

```json
{
  "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](#d-pageresponse) for the new clone.

**Possible Error Responses**:

| Status | Scenario |
|--------|----------|
| `401` | No or expired token |
| `403` | Not the event organizer |
| `404` | Event not found or page not found |

---

## Field Management

---

## 10. Add Field
**Purpose**: Adds a single field to a page. `displayOrder` is auto-assigned. For `DROPDOWN`, `RADIO`, and `CHECKBOX` types, options can be passed inline in the same request or added separately via the Add Option endpoint.
**Endpoint**: `POST` `/api/v1/e-events/applicant-form/events/{eventId}/pages/{pageId}/fields`
**Access Level**: 🔒 Protected (Event organizer only)
**Authentication**: Bearer Token

**Request Headers**:
| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <token>` |
| `Content-Type` | string | Yes | `application/json` |

**Path Parameters**:
| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `eventId` | UUID | Yes | The event | Must be a valid UUID |
| `pageId` | UUID | Yes | The page to add the field to | Must be a valid UUID |

**Request Body Parameters**:
| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `type` | string | Yes | Field type | Enum: `TEXT`, `TEXTAREA`, `EMAIL`, `PHONE`, `NUMBER`, `URL`, `DATE`, `TIME`, `DATETIME`, `DROPDOWN`, `RADIO`, `CHECKBOX`, `FILE`, `RATING`, `HEADER` |
| `label` | string | Yes | Field label (or section title for `HEADER`) | Max: 255 characters |
| `description` | string | No | Helper text | Max: 500 characters |
| `placeholder` | string | No | Input placeholder | Max: 255 characters |
| `required` | boolean | No | Required flag. Forced `false` for `HEADER` | Defaults to `false` |
| `validation` | object | No | Validation rules | See FieldValidation |
| `options` | array | No | Inline options for `DROPDOWN`, `RADIO`, `CHECKBOX`. Ignored for other types. Blank labels are skipped | — |
| `options[].label` | string | Yes (if options provided) | Option label | Max: 255 characters |

---

### Example 1 — TEXT field with validation

**Request**:
```json
{
  "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**:
```json
{
  "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**:
```json
{
  "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**:
```json
{
  "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**:
```json
{
  "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**:
```json
{
  "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**:
```json
{
  "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**:
```json
{
  "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**:
```json
{
  "type": "HEADER",
  "label": "Emergency Contact Information"
}
```

**Response**:
```json
{
  "success": true,
  "httpStatus": "CREATED",
  "message": "Field added",
  "action_time": "2025-02-17T10:30:45",
  "data": {
    "fieldId": "a0b1c2d3-e4f5-6a7b-8c9d-0e1f2a3b4c5d",
    "type": "HEADER",
    "label": "Emergency Contact Information",
    "description": null,
    "placeholder": null,
    "displayOrder": 5,
    "required": false,
    "validation": null,
    "options": []
  }
}
```

---

**Possible Error Responses**:
| Status | Scenario |
|--------|----------|
| `401` | No or expired token |
| `403` | Not the event organizer |
| `404` | Event not found or page not found |
| `422` | `type` is null, or `label` is blank |

---

## 11. Update Field
**Purpose**: Updates the metadata of an existing field. All fields are optional. **Field type cannot be changed** — if a different type is needed, delete the field and create a new one. Options are managed via dedicated option endpoints.
**Endpoint**: `PUT` `/api/v1/e-events/applicant-form/events/{eventId}/fields/{fieldId}`
**Access Level**: 🔒 Protected (Event organizer only)
**Authentication**: Bearer Token

**Request Headers**:
| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <token>` |
| `Content-Type` | string | Yes | `application/json` |

**Path Parameters**:
| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `eventId` | UUID | Yes | The event | Must be a valid UUID |
| `fieldId` | UUID | Yes | The field to update | Must be a valid UUID |

**Request JSON Sample**:
```json
{
  "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**:
```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Field updated",
  "action_time": "2025-02-17T10:30:45",
  "data": { "...": "FieldResponse object" }
}
```

**Success Response Fields**: `data` is a [FieldResponse](#e-fieldresponse).

**Possible Error Responses**:
| Status | Scenario |
|--------|----------|
| `401` | No or expired token |
| `403` | Not the event organizer |
| `404` | Event not found or field not found |
| `400` | Attempting to change field `type` |

---

## 12. Delete Field

**Purpose**: Deletes a field. Soft delete (default) preserves historical answer snapshots. Hard delete permanently removes the field record.

**Endpoint**: `DELETE` `/api/v1/e-events/applicant-form/events/{eventId}/fields/{fieldId}`

**Access Level**: 🔒 Protected (Event organizer only)

**Authentication**: Bearer Token

**Request Headers**:

| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <token>` |

**Path Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `eventId` | UUID | Yes | The event | Must be a valid UUID |
| `fieldId` | UUID | Yes | The field to delete | Must be a valid UUID |

**Query Parameters**:

| Parameter | Type | Required | Description | Validation | Default |
|-----------|------|----------|-------------|------------|---------|
| `hard` | boolean | No | `true` = permanent delete | — | `false` |

**Success Response JSON Sample**:

```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Field deleted",
  "action_time": "2025-02-17T10:30:45",
  "data": null
}
```

> When `hard=true`, `message` is `"Field permanently deleted"`.

**Possible Error Responses**:

| Status | Scenario |
|--------|----------|
| `401` | No or expired token |
| `403` | Not the event organizer |
| `404` | Event not found or field not found |

---

## 13. Bulk Add Fields
**Purpose**: Adds multiple fields to a page in one call. Fields are appended after existing fields in array order. For `DROPDOWN`, `RADIO`, and `CHECKBOX` types, options can be passed inline per field.
**Endpoint**: `POST` `/api/v1/e-events/applicant-form/events/{eventId}/pages/{pageId}/fields/bulk`
**Access Level**: 🔒 Protected (Event organizer only)
**Authentication**: Bearer Token

**Request Headers**:
| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <token>` |
| `Content-Type` | string | Yes | `application/json` |

**Path Parameters**:
| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `eventId` | UUID | Yes | The event | Must be a valid UUID |
| `pageId` | UUID | Yes | The page to add fields to | Must be a valid UUID |

**Request JSON Sample**:
```json
{
  "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**:
```json
{
  "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](#h-bulkoperationresult).

**Possible Error Responses**:
| Status | Scenario |
|--------|----------|
| `401` | No or expired token |
| `403` | Not the event organizer |
| `404` | Event not found or page not found |

---

## 14. Bulk Delete Fields

**Purpose**: Deletes multiple fields from a page in one call. Supports soft and hard delete via `?hard`.

**Endpoint**: `DELETE` `/api/v1/e-events/applicant-form/events/{eventId}/pages/{pageId}/fields/bulk`

**Access Level**: 🔒 Protected (Event organizer only)

**Authentication**: Bearer Token

**Request Headers**:

| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <token>` |
| `Content-Type` | string | Yes | `application/json` |

**Path Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `eventId` | UUID | Yes | The event | Must be a valid UUID |
| `pageId` | UUID | Yes | The page containing the fields | Must be a valid UUID |

**Query Parameters**:

| Parameter | Type | Required | Description | Validation | Default |
|-----------|------|----------|-------------|------------|---------|
| `hard` | boolean | No | `true` = permanent delete | — | `false` |

**Request JSON Sample**:

```json
{
  "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**:

```json
{
  "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](#i-bulkdeleteresult).

**Possible Error Responses**: Same as Endpoint 8 (401, 403, 404).

---

## 15. Bulk Update Fields

**Purpose**: Updates `required` flag and/or `validation` rules across multiple fields at once.

**Endpoint**: `PATCH` `/api/v1/e-events/applicant-form/events/{eventId}/fields/bulk`

**Access Level**: 🔒 Protected (Event organizer only)

**Authentication**: Bearer Token

**Request Headers**:

| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <token>` |
| `Content-Type` | string | Yes | `application/json` |

**Path Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `eventId` | UUID | Yes | The event | Must be a valid UUID |

**Request JSON Sample**:

```json
{
  "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**:

```json
{
  "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](#h-bulkoperationresult).

**Possible Error Responses**:

| Status | Scenario |
|--------|----------|
| `401` | No or expired token |
| `403` | Not the event organizer |
| `404` | One or more field IDs not found — reported in `data.errors[]` |

---

## 16. Clone Field

**Purpose**: Creates a copy of a field (with its options) and appends it to a target page. Label gets "(Copy)" appended.

**Endpoint**: `POST` `/api/v1/e-events/applicant-form/events/{eventId}/fields/{fieldId}/clone`

**Access Level**: 🔒 Protected (Event organizer only)

**Authentication**: Bearer Token

**Request Headers**:

| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <token>` |

**Path Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `eventId` | UUID | Yes | The event | Must be a valid UUID |
| `fieldId` | UUID | Yes | The field to clone | Must be a valid UUID |

**Query Parameters**:

| Parameter | Type | Required | Description | Validation | Default |
|-----------|------|----------|-------------|------------|---------|
| `targetPageId` | UUID | Yes | The page to clone the field into | Must be a valid UUID | — |

**Success Response JSON Sample**:

```json
{
  "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](#e-fieldresponse) for the new clone.

**Possible Error Responses**:

| Status | Scenario |
|--------|----------|
| `401` | No or expired token |
| `403` | Not the event organizer |
| `404` | Event not found, field not found, or target page not found |

---

## Option Management

---

## 17. Add Option

**Purpose**: Adds a choice option to a `DROPDOWN`, `RADIO`, or `CHECKBOX` field. Calling this on any other field type returns `400`.

**Endpoint**: `POST` `/api/v1/e-events/applicant-form/events/{eventId}/fields/{fieldId}/options`

**Access Level**: 🔒 Protected (Event organizer only)

**Authentication**: Bearer Token

**Request Headers**:

| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <token>` |
| `Content-Type` | string | Yes | `application/json` |

**Path Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `eventId` | UUID | Yes | The event | Must be a valid UUID |
| `fieldId` | UUID | Yes | The field to add an option to | Must be `DROPDOWN`, `RADIO`, or `CHECKBOX` type |

**Request JSON Sample**:

```json
{
  "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**:

```json
{
  "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](#f-optionresponse).

**Possible Error Responses**:

| Status | Scenario |
|--------|----------|
| `401` | No or expired token |
| `400` | Field type does not support options |
| `403` | Not the event organizer |
| `404` | Event not found or field not found |
| `422` | `label` is blank |

---

## 18. Update Option

**Purpose**: Updates the label of an existing option.

**Endpoint**: `PUT` `/api/v1/e-events/applicant-form/events/{eventId}/options/{optionId}`

**Access Level**: 🔒 Protected (Event organizer only)

**Authentication**: Bearer Token

**Request Headers**:

| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <token>` |
| `Content-Type` | string | Yes | `application/json` |

**Path Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `eventId` | UUID | Yes | The event | Must be a valid UUID |
| `optionId` | UUID | Yes | The option to update | Must be a valid UUID |

**Request JSON Sample**:

```json
{
  "label": "Public Transport"
}
```

**Request Body Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `label` | string | No | Updated label | Max: 255 characters |

**Success Response JSON Sample**:

```json
{
  "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](#f-optionresponse).

**Possible Error Responses**:

| Status | Scenario |
|--------|----------|
| `401` | No or expired token |
| `403` | Not the event organizer |
| `404` | Event not found or option not found |

---

## 19. Delete Option

**Purpose**: Deletes an option. Soft delete (default) preserves previously selected answer values. Hard delete permanently removes the option record.

**Endpoint**: `DELETE` `/api/v1/e-events/applicant-form/events/{eventId}/options/{optionId}`

**Access Level**: 🔒 Protected (Event organizer only)

**Authentication**: Bearer Token

**Request Headers**:

| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <token>` |

**Path Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `eventId` | UUID | Yes | The event | Must be a valid UUID |
| `optionId` | UUID | Yes | The option to delete | Must be a valid UUID |

**Query Parameters**:

| Parameter | Type | Required | Description | Validation | Default |
|-----------|------|----------|-------------|------------|---------|
| `hard` | boolean | No | `true` = permanent delete | — | `false` |

**Success Response JSON Sample**:

```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Option deleted",
  "action_time": "2025-02-17T10:30:45",
  "data": null
}
```

> When `hard=true`, `message` is `"Option permanently deleted"`.

**Possible Error Responses**:

| Status | Scenario |
|--------|----------|
| `401` | No or expired token |
| `403` | Not the event organizer |
| `404` | Event not found or option not found |

---

## 20. Bulk Delete Options

**Purpose**: Deletes multiple options from a field in one call. Supports soft and hard delete via `?hard`.

**Endpoint**: `DELETE` `/api/v1/e-events/applicant-form/events/{eventId}/fields/{fieldId}/options/bulk`

**Access Level**: 🔒 Protected (Event organizer only)

**Authentication**: Bearer Token

**Request Headers**:

| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <token>` |
| `Content-Type` | string | Yes | `application/json` |

**Path Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `eventId` | UUID | Yes | The event | Must be a valid UUID |
| `fieldId` | UUID | Yes | The field containing the options | Must be a valid UUID |

**Query Parameters**:

| Parameter | Type | Required | Description | Validation | Default |
|-----------|------|----------|-------------|------------|---------|
| `hard` | boolean | No | `true` = permanent delete | — | `false` |

**Request JSON Sample**:

```json
{
  "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**:

```json
{
  "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](#i-bulkdeleteresult).

**Possible Error Responses**:

| Status | Scenario |
|--------|----------|
| `401` | No or expired token |
| `403` | Not the event organizer |
| `404` | Event not found or field not found. Individual errors in `data.errors[]` |

---

## 21. Bulk Update Options

**Purpose**: Updates the labels of multiple options in one call.

**Endpoint**: `PATCH` `/api/v1/e-events/applicant-form/events/{eventId}/options/bulk`

**Access Level**: 🔒 Protected (Event organizer only)

**Authentication**: Bearer Token

**Request Headers**:

| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <token>` |
| `Content-Type` | string | Yes | `application/json` |

**Path Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `eventId` | UUID | Yes | The event | Must be a valid UUID |

**Request JSON Sample**:

```json
{
  "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**:

```json
{
  "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](#h-bulkoperationresult).

**Possible Error Responses**:

| Status | Scenario |
|--------|----------|
| `401` | No or expired token |
| `403` | Not the event organizer |
| `404` | An option ID not found — reported in `data.errors[]` |

---

## Preview

---

## 23. Get Preview Metadata

**Purpose**: Returns a lightweight summary of the form structure — total pages, page numbers, and titles — without loading full field data. Used by the attendee to render a progress indicator before filling. Also returns the attendee's current response state (`responseId`, `currentPageIndex`, `completedPageIds`) if they have an existing draft.

**Endpoint**: `GET` `/api/v1/e-events/applicant-form/events/{eventId}/preview/metadata`

**Access Level**: 🔒 Protected (Organizer always; attendee when event is PUBLISHED)

**Authentication**: Bearer Token

**Request Headers**:

| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <token>` |

**Path Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `eventId` | UUID | Yes | The event | Must be a valid UUID |

**Success Response JSON Sample**:

```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Metadata retrieved",
  "data": {
    "formId": "uuid",
    "formTitle": "Attendee Questions - Tech Summit 2025",
    "totalPages": 3,
    "pageNumbers": [
      { "pageNumber": 1, "pageTitle": "Personal Info" },
      { "pageNumber": 2, "pageTitle": "Experience" },
      { "pageNumber": 3, "pageTitle": "Preferences" }
    ],
    "responseId": "uuid",
    "responseStatus": "DRAFT",
    "currentPageIndex": 1,
    "completedPageIds": ["page-uuid-1"]
  }
}
```

**Success Response Fields**:

| Field | Description |
|-------|-------------|
| `formId` | Internal form ID |
| `formTitle` | Form title |
| `totalPages` | Total active pages |
| `pageNumbers[].pageNumber` | 1-based page number |
| `pageNumbers[].pageTitle` | Page heading |

**Possible Error Responses**:

| Status | Scenario |
|--------|----------|
| `401` | No or expired token |
| `403` | Not the event organizer |
| `404` | Event not found or form not enabled |

---

## 24. Preview Page by Number

**Purpose**: Returns a specific page by its 1-based position number, including all its active fields and options. Used by attendees to load each page as they fill the form. Also accessible to the organizer for previewing before publishing.

**Endpoint**: `GET` `/api/v1/e-events/applicant-form/events/{eventId}/preview/pages/{pageNumber}`

**Access Level**: 🔒 Protected (Organizer always; attendee when event is PUBLISHED)

**Authentication**: Bearer Token

**Request Headers**:

| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <token>` |

**Path Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `eventId` | UUID | Yes | The event | Must be a valid UUID |
| `pageNumber` | integer | Yes | 1-based page position | Must be ≥ 1 and ≤ total active pages |

**Success Response JSON Sample**:

```json
{
  "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](#d-pageresponse).

**Possible Error Responses**:

| Status | Scenario |
|--------|----------|
| `401` | No or expired token |
| `403` | Not the event organizer |
| `404` | Event not found, form not enabled, or `pageNumber` out of range |

---

## 25. Validate Preview Page Answers

**Purpose**: Runs validation on a set of answers for a specific page number without creating or saving any data. Lets the organizer test field validation rules before the form goes live.

**Endpoint**: `POST` `/api/v1/e-events/applicant-form/events/{eventId}/preview/pages/{pageNumber}/validate`

**Access Level**: 🔒 Protected (Organizer always; attendee when event is PUBLISHED)

**Authentication**: Bearer Token

**Request Headers**:

| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <token>` |
| `Content-Type` | string | Yes | `application/json` |

**Path Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `eventId` | UUID | Yes | The event | Must be a valid UUID |
| `pageNumber` | integer | Yes | 1-based page number to validate against | Must be ≥ 1 and ≤ total active pages |

**Request JSON Sample**:

```json
{
  "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**:

```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Validation complete",
  "action_time": "2025-02-17T10:30:45",
  "data": {
    "valid": false,
    "errors": [
      {
        "pageId": "1a2b3c4d-...",
        "pageTitle": "Attendee Information",
        "fieldId": "fieldId-fullname",
        "fieldLabel": "Full Name",
        "errorMessage": "Full Name is required",
        "errorType": "REQUIRED"
      },
      {
        "pageId": "1a2b3c4d-...",
        "pageTitle": "Attendee Information",
        "fieldId": "fieldId-email",
        "fieldLabel": "Email",
        "errorMessage": "Email must be valid email",
        "errorType": "INVALID_FORMAT"
      }
    ]
  }
}
```

**Success Response Fields**:

| Field | Description |
|-------|-------------|
| `data.valid` | `true` if all fields passed validation |
| `data.errors[].pageId` | Page UUID |
| `data.errors[].pageTitle` | Page title |
| `data.errors[].fieldId` | Field UUID |
| `data.errors[].fieldLabel` | Field label |
| `data.errors[].errorMessage` | Human-readable error |
| `data.errors[].errorType` | `REQUIRED`, `INVALID_FORMAT`, `INVALID_TYPE`, `VALIDATION_FAILED` |

**Possible Error Responses**:

| Status | Scenario |
|--------|----------|
| `401` | No or expired token |
| `403` | Not the event organizer |
| `404` | Event not found, form not enabled, or `pageNumber` out of range |

---

## Attendee Submission

---

## 26. Start Submission

**Purpose**: Initiates the attendee's form response session. Creates a new `DRAFT` response tied to this event's form. If the attendee already has a draft or submitted response for this event's form, the existing response is returned — this endpoint is idempotent. Requires the event to be `PUBLISHED` and within its registration window.

**Endpoint**: `POST` `/api/v1/e-events/applicant-form/events/{eventId}/start`

**Access Level**: 🔒 Protected (Any authenticated attendee)

**Authentication**: Bearer Token

**Request Headers**:

| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <token>` |

**Path Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `eventId` | UUID | Yes | The published event | Must be a valid UUID |

**Success Response JSON Sample**:

```json
{
  "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](#g-formresponseobject-attendee-submission).

**Possible Error Responses**:

| Status | Scenario |
|--------|----------|
| `401` | No or expired token |
| `403` | Event not published, registration not yet open, or registration already closed |
| `404` | Event not found or form not enabled |

---

## 27. Save Page Answers

**Purpose**: Saves the attendee's answers for a specific page. Behaviour is controlled by the `?moveToNextPage` query parameter which maps directly to two distinct client actions — background auto-save and the "Next" button.

**Endpoint**: `PUT` `/api/v1/e-events/applicant-form/events/{eventId}/pages/{pageId}/save`

**Access Level**: 🔒 Protected (Attendee — response owner)

**Authentication**: Bearer Token

**Request Headers**:

| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <token>` |
| `Content-Type` | string | Yes | `application/json` |

**Path Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `eventId` | UUID | Yes | The event | Must be a valid UUID |
| `pageId` | UUID | Yes | The page being answered | Must be a valid UUID belonging to this event's form |

**Query Parameters**:

| Parameter | Type | Required | Description | Default |
|-----------|------|----------|-------------|---------|
| `moveToNextPage` | boolean | No | Controls validation and page advancement. See behaviour table below | `false` |

#### `moveToNextPage` Behaviour

| Value | Intended Use | Validation | What Happens on Success | What Happens on Failure |
|-------|-------------|-----------|------------------------|------------------------|
| `false` | Background auto-save while the user is typing | None — answers are stored as-is regardless of format or required state | Answers written. `completedPageIds` unchanged. Always `success: true` | N/A — cannot fail |
| `true` | User clicks "Next" to advance to the next page | Full — all fields on the page are validated (required + format + type) | Answers written, page added to `completedPageIds`, `currentPageIndex` advances. `success: true` | Nothing saved, nothing advanced. `success: false` with `errors[]` in `data` |

> **Key rule**: A page only enters `completedPageIds` through a successful `?moveToNextPage=true` call. `submit` will reject the form if any page is missing from `completedPageIds`.

**Request Body**: A flat `Map<UUID, AnswerValue>` where each key is a `fieldId`.

| Key | Type | Description |
|-----|------|-------------|
| `{fieldId}` | UUID (map key) | The field being answered |
| `.value` | any | Answer value. See answer value types below |
| `.fileUrl` | string | FILE fields — uploaded file URL |
| `.fileName` | string | FILE fields — original file name |
| `.fileSize` | long | FILE fields — file size in bytes |
| `.fileType` | string | FILE fields — MIME type |

#### Answer Value Types

| Field Type | Expected Value |
|------------|---------------|
| `TEXT`, `TEXTAREA`, `EMAIL`, `PHONE`, `URL` | String |
| `NUMBER` | Number (integer or decimal) |
| `DATE` | String — `YYYY-MM-DD` |
| `TIME` | String — `HH:mm` |
| `DATETIME` | String — ISO 8601 datetime |
| `DROPDOWN`, `RADIO` | String — UUID of selected option |
| `CHECKBOX` | Array of strings — UUIDs of selected options |
| `RATING` | Integer — `1` to `5` |
| `FILE` | `null` — metadata goes in `fileUrl`, `fileName`, etc. |
| `HEADER` | Omit entirely — no answer needed |

---

### Example 1 — Auto-save (`moveToNextPage=false`)

**Request**:
```
PUT /events/{eventId}/pages/{pageId}/save?moveToNextPage=false
```
```json
{
  "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:
```json
{
  "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
```
```json
{
  "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:
```json
{
  "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
```
```json
{
  "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:
```json
{
  "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](#j-savepageresponse).

**Possible Error Responses**:

| Status | Scenario |
|--------|----------|
| `401` | No or expired token |
| `403` | Registration window closed or event not published |
| `404` | Event not found, form not enabled, or no active draft found — call `/start` first |

---

## 28. Get My Response

**Purpose**: Returns the authenticated attendee's current response for the event's form — whether still in draft or already submitted.

**Endpoint**: `GET` `/api/v1/e-events/applicant-form/events/{eventId}/my-response`

**Access Level**: 🔒 Protected (Any authenticated attendee)

**Authentication**: Bearer Token

**Request Headers**:

| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <token>` |

**Path Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `eventId` | UUID | Yes | The event | Must be a valid UUID |

**Success Response JSON Sample**:

```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Response retrieved",
  "action_time": "2025-02-17T10:30:45",
  "data": { "...": "Full FormResponseObject" }
}
```

**Success Response Fields**: `data` is a [FormResponseObject](#g-formresponseobject-attendee-submission).

**Possible Error Responses**:

| Status | Scenario |
|--------|----------|
| `401` | No or expired token |
| `404` | No response found for this attendee on this event's form |

---

## 29. Submit

**Purpose**: Finalises and submits the attendee's response. The server performs a two-stage validation before accepting: first it checks that every page is in `completedPageIds` (meaning the attendee clicked "Next" and passed validation for each page), then it checks that all required fields still have answers. If either check fails the response is rejected with `422` and a list of field-level errors in `data`. On success, `status` transitions to `SUBMITTED` and `completionTimeSeconds` is recorded.

**Endpoint**: `POST` `/api/v1/e-events/applicant-form/events/{eventId}/submit`

**Access Level**: 🔒 Protected (Attendee — response owner)

**Authentication**: Bearer Token

**Request Headers**:

| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <token>` |

**Path Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `eventId` | UUID | Yes | The event | Must be a valid UUID |

**Success Response JSON Sample**:

```json
{
  "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](#g-formresponseobject-attendee-submission). `data.status` will be `"SUBMITTED"`.

**Error Response — page not completed via "Next" (`422`)**:
```json
{
  "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`)**:
```json
{
  "success": false,
  "httpStatus": "UNPROCESSABLE_ENTITY",
  "message": "Please complete all required pages",
  "action_time": "2026-05-22T14:34:00",
  "data": [
    {
      "pageId": "a64a2f29-428e-41fc-8d14-043841d857a2",
      "pageTitle": "Personal Info",
      "fieldId": "956b1e2e-3e47-4ba9-8248-a6668305ca42",
      "fieldLabel": "Phone Number",
      "errorType": "REQUIRED",
      "errorMessage": "This field is required"
    }
  ]
}
```

**Possible Error Responses**:

| Status | Scenario |
|--------|----------|
| `401` | No or expired token |
| `403` | Already submitted, or registration window closed |
| `404` | Event not found, form not enabled, or no draft response found |
| `422` | One or more pages not in `completedPageIds` (`PAGE_INCOMPLETE`); or a required field has no answer (`REQUIRED`). Error list is in `data[]` |

---

## Organizer Response Management

---

## 30. Get Response by ID

**Purpose**: Retrieves a specific attendee response by ID. Available to the event organizer only.

**Endpoint**: `GET` `/api/v1/e-events/applicant-form/events/{eventId}/responses/{responseId}`

**Access Level**: 🔒 Protected (Event organizer only)

**Authentication**: Bearer Token

**Request Headers**:

| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <token>` |

**Path Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `eventId` | UUID | Yes | The event | Must be a valid UUID |
| `responseId` | UUID | Yes | The response to retrieve | Must be a valid UUID |

**Success Response JSON Sample**:

```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Response retrieved",
  "action_time": "2025-02-17T10:30:45",
  "data": { "...": "Full FormResponseObject" }
}
```

**Success Response Fields**: `data` is a [FormResponseObject](#g-formresponseobject-attendee-submission).

**Possible Error Responses**:

| Status | Scenario |
|--------|----------|
| `401` | No or expired token |
| `403` | Not the event organizer |
| `404` | Event not found or response not found |

---

## 31. Get All Responses

**Purpose**: Returns a paginated list of all attendee responses for the event's form. For organizer review and export.

**Endpoint**: `GET` `/api/v1/e-events/applicant-form/events/{eventId}/responses`

**Access Level**: 🔒 Protected (Event organizer only)

**Authentication**: Bearer Token

**Request Headers**:

| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <token>` |

**Path Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `eventId` | UUID | Yes | The event | Must be a valid UUID |

**Query Parameters**:

| Parameter | Type | Required | Description | Validation | Default |
|-----------|------|----------|-------------|------------|---------|
| `page` | integer | No | Page number (0-based) | Min: 0 | `0` |
| `size` | integer | No | Items per page | Min: 1 | `20` |

> **Note**: This endpoint uses **0-based** pagination (`page=0` is the first page), matching the Spring `Pageable` default passed from the controller.

**Success Response JSON Sample**:

```json
{
  "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](#g-formresponseobject-attendee-submission) entries.

**Possible Error Responses**:

| Status | Scenario |
|--------|----------|
| `401` | No or expired token |
| `403` | Not the event organizer |
| `404` | Event not found or form not enabled |

---

## 32. Get Analytics

**Purpose**: Returns full submission analytics for the event's form — stats overview, per-field distributions, and daily submission trends.

**Endpoint**: `GET` `/api/v1/e-events/applicant-form/events/{eventId}/analytics`

**Access Level**: 🔒 Protected (Event organizer only)

**Authentication**: Bearer Token

**Request Headers**:

| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <token>` |

**Path Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `eventId` | UUID | Yes | The event | Must be a valid UUID |

**Success Response JSON Sample**:

```json
{
  "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](#k-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` |