# Events  Management API

**Author**: Josh S. Sakweli, Backend Lead Team
**Last Updated**: 2026-05-25
**Version**: v1.2

**Base URL**: `https://your-api-domain.com/api/v1/e-events`

**Short Description**: The Event Core API provides full lifecycle management for events on the NextGate platform — from creating drafts and configuring schedules, to publishing, managing live events, and discovery. It is used by event organizers to build, publish, and manage events step-by-step, and by all authenticated users to browse, search, and filter published events.

**Hints**:
- All write operations (create, update, publish) require a valid Bearer token. Read operations on published events are public.
- Dates and times must always use ISO 8601 format with timezone offset (e.g., `2025-06-15T09:00:00+03:00`). The API stores and returns `ZonedDateTime`.
- Event creation follows a **staged workflow**: `BASIC_INFO → SCHEDULE → LOCATION_DETAILS → TICKETS`. All required stages must be completed before publishing.
- Slugs are auto-generated from the event title with a UUID suffix to guarantee uniqueness — do not pass a slug manually.
- Pagination uses 1-based page numbers (page=1 is the first page).
- Category seeding and management live in the **Event Categories API** (base: `/api/v1/e-events/categories`), documented separately.

---

### Event Creation User Journey

```
  [Organizer]
       │
       │  POST /draft
       ▼
  ┌─────────────┐
  │  BASIC INFO │  title, category, format, description, media, ctaLabel (optional)
  └──────┬──────┘
         │  PATCH /drafts/{id}/basic-info  (optional update)
         │
         │  PATCH /draft/{id}/schedule
         ▼
  ┌──────────────┐
  │   SCHEDULE   │  days[ date, startTime, endTime ], timezone
  └──────┬───────┘
         │
         │  PATCH /draft/{id}/location
         ▼
  ┌──────────────────┐
  │ LOCATION DETAILS │  venue (IN_PERSON/HYBRID) or virtualDetails (ONLINE/HYBRID)
  └──────┬───────────┘         or skip entirely (TBA format)
         │
         │  (Ticket creation via Tickets API — required before publish)
         ▼
  ┌──────────────┐
  │   TICKETS    │  at least one active ticket required
  └──────┬───────┘
         │
         │  (Optional enrichment)
         │  PATCH /drafts/{id}/highlights
         │  PATCH /drafts/{id}/faqs
         │  PATCH /drafts/{id}/lineup
         │  PATCH /drafts/{id}/agenda
         │  POST  /draft/{id}/products/{productId}
         │  POST  /draft/{id}/shops/{shopId}
         │
         │  PATCH /{eventId}/publish
         ▼
  ┌───────────────┐
  │   PUBLISHED   │  RSA keys generated, ctaLabel auto-derived if not set,
  │               │  category count incremented, event visible in public feed
  └───────────────┘
```

### Event Status Flow

```
  DRAFT ◄──────────────────────► PUBLISHED
    │           (unpublish           │
    │        if 0 tickets sold)      │  (unpublish — only if no tickets sold)
    │                                │
    │  (discardDraft)                │
    ▼                                ▼
  [deleted]                     CANCELLED ◄─── (cancel from any non-terminal status)
                                     │
                              [terminal state]

  PUBLISHED ──► (system / scheduled job) ──► HAPPENING ──► COMPLETED
```

> **Status Rules**:
> - `DRAFT ↔ PUBLISHED` — Free movement. Unpublish is only allowed if zero tickets have been sold.
> - `CANCELLED` — Terminal. Can be triggered from any non-terminal status. Triggers bulk refund if tickets were sold.
> - `HAPPENING` / `COMPLETED` — System-managed via scheduled jobs.

---

## Standard Response Format

All API responses follow a consistent structure using the Globe Response Builder pattern.

### Success Response Structure

```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Operation completed successfully",
  "action_time": "2025-09-23T10:30:45",
  "data": { }
}
```

### Error Response Structure

```json
{
  "success": false,
  "httpStatus": "BAD_REQUEST",
  "message": "Error description",
  "action_time": "2025-09-23T10:30:45",
  "data": "Error description"
}
```

### Standard Response Fields

| Field | Type | Description |
|-------|------|-------------|
| `success` | boolean | `true` for successful operations, `false` for errors |
| `httpStatus` | string | HTTP status name (OK, CREATED, BAD_REQUEST, etc.) |
| `message` | string | Human-readable description of the result |
| `action_time` | string | ISO 8601 timestamp of when the response was generated |
| `data` | object / string | Response payload on success; error detail on failure |

---

## Shared Response Object Definitions

The following objects are returned by multiple endpoints. They are defined once here and referenced throughout.

### A. EventResponse (Full Event Object)

Returned by all draft and event management endpoints.

| Field | Type | Description |
|-------|------|-------------|
| `id` | UUID | Unique event identifier |
| `title` | string | Event title |
| `slug` | string | URL-friendly identifier, auto-generated |
| `description` | string | Full event description |
| `category.categoryId` | UUID | Category identifier |
| `category.categoryName` | string | Category display name |
| `category.categorySlug` | string | Category slug |
| `eventFormat` | string | `IN_PERSON`, `ONLINE`, `HYBRID`, or `TBA` |
| `eventVisibility` | string | `PUBLIC`, `PRIVATE`, or `UNLISTED` |
| `status` | string | `DRAFT`, `PUBLISHED`, `HAPPENING`, `CANCELLED`, `COMPLETED` |
| `schedule.startDateTime` | ZonedDateTime | Event start (ISO 8601 with offset) |
| `schedule.endDateTime` | ZonedDateTime | Event end (ISO 8601 with offset) |
| `schedule.timezone` | string | IANA timezone string (e.g., `Africa/Dar_es_Salaam`) |
| `schedule.days[]` | array | Day-level schedule entries (see EventDayInfo below) |
| `venue.name` | string | Venue name (IN_PERSON / HYBRID only) |
| `venue.address` | string | Venue address |
| `venue.coordinates.latitude` | string | Latitude decimal string |
| `venue.coordinates.longitude` | string | Longitude decimal string |
| `virtualDetails.meetingLink` | string | Meeting URL (ONLINE / HYBRID only) |
| `virtualDetails.meetingId` | string | Platform meeting ID |
| `virtualDetails.passcode` | string | Meeting passcode |
| `media.banner` | string | Banner image URL |
| `media.thumbnail` | string | Thumbnail image URL |
| `media.gallery[]` | array | List of gallery image URLs |
| `highlights[]` | array | See HighlightEntry definition below |
| `faqs[]` | array | See FaqEntry definition below |
| `lineup[]` | array | See LineupEntry definition below |
| `agenda[]` | array | See AgendaDay definition below |
| `linkedProducts[]` | array | `{ productId, productName, productSlug }` |
| `linkedShops[]` | array | `{ shopId, shopName, shopSlug }` |
| `tickets[]` | array | See TicketSummaryInfo definition below |
| `organizer.organizerId` | UUID | Organizer account ID |
| `organizer.organizerName` | string | Organizer full name |
| `organizer.organizerUsername` | string | Organizer system username |
| `ctaLabel` | string | CTA button label (e.g., "Get Tickets", "Register for Free"). Auto-derived from ticket pricing at publish if not explicitly set |
| `hasApplicantForm` | boolean | Whether an applicant form is configured for this event |
| `applicantForm.displayTime` | string | When the form is shown: `BEFORE_CHECKOUT` or `AFTER_CHECKOUT` |
| `applicantForm.isRequired` | boolean | Whether form submission is required for online attendees |
| `applicantForm.applyToAtDoor` | boolean | Whether the form also applies to at-door check-in |
| `currentStage` | string | Current event creation stage |
| `completedStages[]` | array | List of completed stage names |
| `completionPercentage` | integer | 0–100 completion percentage |
| `canPublish` | boolean | Whether all required stages are completed |
| `createdAt` | ZonedDateTime | Creation timestamp |
| `updatedAt` | ZonedDateTime | Last update timestamp |
| `createdBy` | string | Username of creator |
| `updatedBy` | string | Username of last editor |

#### EventDayInfo

| Field | Type | Description |
|-------|------|-------------|
| `id` | UUID | Day entity ID |
| `date` | string | Date in `YYYY-MM-DD` format |
| `startTime` | string | Start time in `HH:mm:ss` |
| `endTime` | string | End time in `HH:mm:ss` |
| `description` | string | Optional day description |
| `dayOrder` | integer | Display order (1-based) |

#### HighlightEntry

| Field | Type | Description |
|-------|------|-------------|
| `type` | string | `AGE_RESTRICTION`, `CHECK_IN_TIME`, `PARKING`, `DRESS_CODE`, `FOOD_DRINKS`, `ACCESSIBILITY`, `REFUND_POLICY`, `WHAT_TO_BRING`, `PROHIBITED_ITEMS`, `WEATHER_INFO`, `CUSTOM` |
| `title` | string | Display title for this highlight |
| `value` | string | Short value (e.g., "18+") |
| `description` | string | Longer explanation |

#### FaqEntry

| Field | Type | Description |
|-------|------|-------------|
| `question` | string | The FAQ question |
| `answer` | string | The answer |
| `order` | integer | Display order |

#### LineupEntry

| Field | Type | Description |
|-------|------|-------------|
| `entryType` | string | `PLATFORM_USER` or `CUSTOM` |
| `userId` | UUID | Platform user ID (only when `entryType=PLATFORM_USER`) |
| `name` | string | Display name (auto-enriched from user profile when `PLATFORM_USER`) |
| `role` | string | `HEADLINER`, `PERFORMER`, `SPEAKER`, `DJ`, `HOST`, `PANELIST`, `MODERATOR`, `GUEST` |
| `title` | string | Professional title (e.g., "Lead Vocalist") |
| `bio` | string | Short biography |
| `image` | string | Profile/headshot image URL |
| `performanceDay` | integer | Which day number of the event they perform |
| `performanceTime` | string | Time of performance |
| `order` | integer | Display order |

#### AgendaDay

| Field | Type | Description |
|-------|------|-------------|
| `dayNumber` | integer | Day number (1-based) |
| `date` | string | Date in `YYYY-MM-DD` format |
| `sessions[]` | array | See AgendaSession definition below |

#### AgendaSession

| Field | Type | Description |
|-------|------|-------------|
| `startTime` | string | Session start time (`HH:mm`) |
| `endTime` | string | Session end time (`HH:mm`) |
| `title` | string | Session title |
| `description` | string | Session description |
| `type` | string | `GENERAL`, `PERFORMANCE`, `CEREMONY`, `PANEL`, `WORKSHOP`, `NETWORKING`, `MEAL`, `BREAK` |
| `location` | string | Sub-location within the event venue |
| `presenterType` | string | `PLATFORM_USER` or `CUSTOM` |
| `presenterId` | UUID | Platform user ID (when `presenterType=PLATFORM_USER`) |
| `presenterName` | string | Presenter name (auto-enriched from user profile when `PLATFORM_USER`) |
| `presenterTitle` | string | Professional title |
| `presenterBio` | string | Short biography |
| `presenterImage` | string | Headshot image URL |

#### TicketSummaryInfo

| Field | Type | Description |
|-------|------|-------------|
| `id` | UUID | Ticket type ID |
| `name` | string | Ticket type name (e.g., "VIP", "General Admission") |
| `price` | BigDecimal | Ticket price (0.00 for free) |
| `totalTickets` | integer | Total slots allocated |
| `ticketsSold` | integer | Number sold |
| `ticketsAvailable` | integer | Remaining slots |
| `isSoldOut` | boolean | True when available = 0 |
| `attendanceMode` | string | Ticket attendance mode enum value |
| `status` | string | Ticket status enum value |
| `isOnSale` | boolean | Whether the ticket is currently within its sales window |
| `saleStatusMessage` | string | Human-readable explanation of the current sale status |

---

### B. EventSummaryResponse (Lightweight List Object)

Returned by all paginated list and search endpoints.

| Field | Type | Description |
|-------|------|-------------|
| `id` | UUID | Event ID |
| `title` | string | Event title |
| `slug` | string | URL slug |
| `shortDescription` | string | First 150 characters of description |
| `categoryId` | UUID | Category ID |
| `categoryName` | string | Category display name |
| `eventFormat` | string | `IN_PERSON`, `ONLINE`, `HYBRID`, `TBA` |
| `eventVisibility` | string | `PUBLIC`, `PRIVATE`, `UNLISTED` |
| `status` | string | Event status |
| `startDateTime` | ZonedDateTime | Start date/time with offset |
| `endDateTime` | ZonedDateTime | End date/time with offset |
| `timezone` | string | IANA timezone |
| `locationSummary` | string | Human-readable location (e.g., "Dar es Salaam, TZ", "Online Event", "Location To Be Announced") |
| `thumbnail` | string | Thumbnail URL |
| `hasApplicantForm` | boolean | Whether an applicant form is configured |
| `ctaLabel` | string | CTA button label |
| `pricing.minPrice` | BigDecimal | Lowest available ticket price |
| `pricing.maxPrice` | BigDecimal | Highest available ticket price |
| `pricing.isFree` | boolean | True when all tickets are free |
| `pricing.hasPaidTickets` | boolean | True when at least one paid ticket exists |
| `organizerId` | UUID | Organizer ID |
| `organizerName` | string | Organizer full name |
| `organizerUsername` | string | Organizer username |
| `stats.totalTickets` | integer | Sum of all ticket slots |
| `stats.ticketsSold` | integer | Total tickets sold |
| `stats.ticketsAvailable` | integer | Remaining tickets |
| `stats.isSoldOut` | boolean | True when no tickets remain |
| `stats.attendeeCount` | integer | Same as ticketsSold |
| `createdAt` | ZonedDateTime | Creation timestamp |

---

### C. Standard Paginated Response Wrapper

All list endpoints return data inside a Spring `Page` wrapper.

```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Events retrieved successfully",
  "action_time": "2025-02-17T10:30:45",
  "data": {
    "content": [ ],
    "pageable": {
      "pageNumber": 0,
      "pageSize": 10
    },
    "totalElements": 42,
    "totalPages": 5,
    "last": false,
    "first": true,
    "empty": false
  }
}
```

> **Note**: All list endpoints accept `page` (1-based, default `1`) and `size` (default `10`) as query parameters. Spring internally converts to 0-based before querying.

---

## HTTP Method Badge Standards

- **GET** — Green (`#28a745`) — Safe, read-only operations
- **POST** — Blue (`#007bff`) — Create new resources
- **PUT** — Yellow (`#ffc107`, black text) — Replace entire resource
- **PATCH** — Orange (`#fd7e14`) — Partial update
- **DELETE** — Red (`#dc3545`) — Remove resource

---

## Standard Error Types

### Application-Level Exceptions (400–499)

| Code | Name | When it occurs |
|------|------|---------------|
| `400` | `BAD_REQUEST` | Invalid request data, already published event, duplicate product/shop, unpublish blocked due to ticket sales |
| `401` | `UNAUTHORIZED` | Missing, expired, or malformed Bearer token |
| `403` | `FORBIDDEN` | Authenticated but not the event organizer, or accessing a draft belonging to another user |
| `404` | `NOT_FOUND` | Event, category, product, or shop ID not found |
| `422` | `UNPROCESSABLE_ENTITY` | Bean validation failures with per-field detail |

### Server-Level Exceptions (500+)

| Code | Name | When it occurs |
|------|------|---------------|
| `500` | `INTERNAL_SERVER_ERROR` | RSA key generation failure, unexpected runtime error |

---

## Shared Error Response Examples

> All endpoints may return these error shapes. Each endpoint section references them rather than repeating the full JSON.

**401 — Unauthorized:**
```json
{
  "success": false,
  "httpStatus": "UNAUTHORIZED",
  "message": "Token has expired",
  "action_time": "2025-02-17T10:30:45",
  "data": "Token has expired"
}
```

**403 — Forbidden:**
```json
{
  "success": false,
  "httpStatus": "FORBIDDEN",
  "message": "Access denied: Insufficient permissions",
  "action_time": "2025-02-17T10:30:45",
  "data": "Access denied: Insufficient permissions"
}
```

**404 — Not Found:**
```json
{
  "success": false,
  "httpStatus": "NOT_FOUND",
  "message": "Event not found with ID: 3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "action_time": "2025-02-17T10:30:45",
  "data": "Event not found with ID: 3fa85f64-5717-4562-b3fc-2c963f66afa6"
}
```

**422 — Validation Error:**
```json
{
  "success": false,
  "httpStatus": "UNPROCESSABLE_ENTITY",
  "message": "Validation failed",
  "action_time": "2025-02-17T10:30:45",
  "data": {
    "title": "size must be between 3 and 200",
    "categoryId": "must not be null",
    "eventFormat": "must not be null"
  }
}
```

---

## Endpoints

---

## 1. Create Event Draft

**Purpose**: Creates a new event in `DRAFT` status as the first step of the event creation workflow. Marks `BASIC_INFO` stage as completed automatically.

**Endpoint**: `POST` `/api/v1/e-events/drafts`

**Access Level**: 🔒 Protected (Any authenticated user — becomes event organizer)

**Authentication**: Bearer Token

**Request Headers**:

| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <token>` |
| `Content-Type` | string | Yes | `application/json` |

**Request JSON Sample**:

```json
{
  "title": "Dar es Salaam Jazz Festival 2025",
  "categoryId": "8e3a1b2c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
  "eventFormat": "IN_PERSON",
  "eventVisibility": "PUBLIC",
  "description": "An annual celebration of jazz and live music at the heart of the city.",
  "media": {
    "banner": "https://cdn.example.com/banners/jazz-2025.jpg",
    "thumbnail": "https://cdn.example.com/thumbs/jazz-2025.jpg",
    "gallery": []
  }
}
```

**Request Body Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `title` | string | Yes | Event title | Min: 3, Max: 200 characters |
| `categoryId` | UUID | Yes | ID of an active event category | Must exist and be active |
| `eventFormat` | string | Yes | Event format | Enum: `IN_PERSON`, `ONLINE`, `HYBRID`, `TBA` |
| `eventVisibility` | string | No | Who can see the event | Enum: `PUBLIC`, `PRIVATE`, `UNLISTED`. Defaults to `PUBLIC` |
| `description` | string | No | Full event description | Max: 5000 characters |
| `media` | object | No | Media URLs for the event | See MediaRequest below |
| `media.banner` | string | No | Banner image URL | Max: 500 characters |
| `media.thumbnail` | string | No | Thumbnail image URL | Max: 500 characters |
| `media.gallery` | array | No | List of gallery image URLs | — |

**Success Response JSON Sample**:

```json
{
  "success": true,
  "httpStatus": "CREATED",
  "message": "Event draft created",
  "action_time": "2025-02-17T10:30:45",
  "data": {
    "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "title": "Dar es Salaam Jazz Festival 2025",
    "slug": "dar-es-salaam-jazz-festival-2025-a1b2c3d4",
    "description": "An annual celebration of jazz...",
    "category": {
      "categoryId": "8e3a1b2c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
      "categoryName": "Music & Concerts",
      "categorySlug": "music-concerts"
    },
    "eventFormat": "IN_PERSON",
    "eventVisibility": "PUBLIC",
    "status": "DRAFT",
    "currentStage": "BASIC_INFO",
    "completedStages": ["BASIC_INFO"],
    "completionPercentage": 25,
    "canPublish": false,
    "schedule": null,
    "venue": null,
    "virtualDetails": null,
    "media": {
      "banner": "https://cdn.example.com/banners/jazz-2025.jpg",
      "thumbnail": "https://cdn.example.com/thumbs/jazz-2025.jpg",
      "gallery": []
    },
    "highlights": null,
    "faqs": null,
    "lineup": null,
    "agenda": null,
    "linkedProducts": [],
    "linkedShops": [],
    "tickets": [],
    "organizer": {
      "organizerId": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
      "organizerName": "Amina Hassan",
      "organizerUsername": "amina.hassan"
    },
    "ctaLabel": null,
    "hasApplicantForm": false,
    "applicantForm": null,
    "createdAt": "2025-02-17T10:30:45+03:00",
    "updatedAt": null,
    "createdBy": "amina.hassan",
    "updatedBy": null
  }
}
```

**Success Response Fields**: See [Shared Response Object A — EventResponse](#a-eventresponse-full-event-object).

**Possible Error Responses**:

| Status | Scenario |
|--------|----------|
| `401` | No or expired token — see [Shared Error: 401](#shared-error-response-examples) |
| `404` | Category ID not found |
| `422` | Missing required fields (title, categoryId, eventFormat) — see [Shared Error: 422](#shared-error-response-examples) |

---

## 2. Get My Drafts

**Purpose**: Returns a paginated list of all DRAFT events owned by the authenticated organizer.

**Endpoint**: `GET` `/api/v1/e-events/drafts`

**Access Level**: 🔒 Protected (Organizer — own drafts only)

**Authentication**: Bearer Token

**Request Headers**:

| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <token>` |

**Query Parameters**:

| Parameter | Type | Required | Description | Validation | Default |
|-----------|------|----------|-------------|------------|---------|
| `page` | integer | No | Page number (1-based) | Min: 1 | `1` |
| `size` | integer | No | Items per page | Min: 1 | `10` |

**Success Response JSON Sample**:

```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Drafts retrieved",
  "action_time": "2025-02-17T10:30:45",
  "data": {
    "content": [
      {
        "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "title": "Dar es Salaam Jazz Festival 2025",
        "slug": "dar-es-salaam-jazz-festival-2025-a1b2c3d4",
        "shortDescription": "An annual celebration of jazz and live music...",
        "categoryId": "8e3a1b2c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
        "categoryName": "Music & Concerts",
        "eventFormat": "IN_PERSON",
        "eventVisibility": "PUBLIC",
        "status": "DRAFT",
        "startDateTime": null,
        "endDateTime": null,
        "timezone": null,
        "locationSummary": null,
        "thumbnail": "https://cdn.example.com/thumbs/jazz-2025.jpg",
        "hasApplicantForm": false,
        "ctaLabel": null,
        "pricing": { "isFree": true, "hasPaidTickets": false },
        "organizerName": "Amina Hassan",
        "organizerUsername": "amina.hassan",
        "stats": { "totalTickets": 0, "ticketsSold": 0, "ticketsAvailable": 0, "isSoldOut": false },
        "createdAt": "2025-02-17T10:30:45+03:00"
      }
    ],
    "totalElements": 3,
    "totalPages": 1,
    "first": true,
    "last": true,
    "empty": false
  }
}
```

**Success Response Fields**: `data.content[]` contains [EventSummaryResponse](#b-eventsummaryresponse-lightweight-list-object) objects. Pagination fields follow [Standard Paginated Response Wrapper](#c-standard-paginated-response-wrapper).

**Possible Error Responses**:

| Status | Scenario |
|--------|----------|
| `401` | No or expired token — see [Shared Error: 401](#shared-error-response-examples) |

---

## 3. Get Draft by ID

**Purpose**: Retrieves the full detail of a specific draft. Only the draft's organizer can access it.

**Endpoint**: `GET` `/api/v1/e-events/drafts/{draftId}`

**Access Level**: 🔒 Protected (Organizer — own drafts only)

**Authentication**: Bearer Token

**Request Headers**:

| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <token>` |

**Path Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `draftId` | UUID | Yes | The ID of the draft event | Must be a valid UUID |

**Success Response JSON Sample**:

```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Draft retrieved",
  "action_time": "2025-02-17T10:30:45",
  "data": { "...": "Full EventResponse object" }
}
```

**Success Response Fields**: `data` is a full [EventResponse](#a-eventresponse-full-event-object).

**Possible Error Responses**:

| Status | Scenario |
|--------|----------|
| `401` | No or expired token |
| `403` | Authenticated but not the owner of this draft |
| `404` | Draft not found with given ID |

---

## 4. Discard Draft

**Purpose**: Permanently deletes a draft event and all its associated day schedules, linked products, and linked shops. This action is irreversible.

**Endpoint**: `DELETE` `/api/v1/e-events/drafts/{draftId}`

**Access Level**: 🔒 Protected (Organizer — own drafts only)

**Authentication**: Bearer Token

**Request Headers**:

| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <token>` |

**Path Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `draftId` | UUID | Yes | The ID of the draft to discard | Must be a valid UUID |

**Success Response JSON Sample**:

```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Draft discarded",
  "action_time": "2025-02-17T10:30:45",
  "data": null
}
```

**Possible Error Responses**:

| Status | Scenario |
|--------|----------|
| `401` | No or expired token |
| `403` | Authenticated but not the owner of this draft |
| `404` | Draft not found with given ID |

---

## 5. Update Draft — Basic Info

**Purpose**: Updates the basic information of a draft. All fields are optional — only provided fields are updated. Advances `currentStage` to `SCHEDULE` upon completion.

**Endpoint**: `PATCH` `/api/v1/e-events/drafts/{draftId}/basic-info`

**Access Level**: 🔒 Protected (Organizer — own drafts only)

**Authentication**: Bearer Token

**Request Headers**:

| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <token>` |
| `Content-Type` | string | Yes | `application/json` |

**Path Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `draftId` | UUID | Yes | The draft to update | Must be a valid UUID |

**Request JSON Sample**:

```json
{
  "title": "Dar es Salaam Jazz Festival 2025 — Updated",
  "description": "The biggest jazz event in East Africa returns for its 10th edition with over 30 artists.",
  "categoryId": "8e3a1b2c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
  "eventVisibility": "PUBLIC",
  "eventFormat": "IN_PERSON",
  "ctaLabel": "Get Tickets",
  "media": {
    "banner": "https://cdn.example.com/banners/jazz-2025-v2.jpg",
    "thumbnail": "https://cdn.example.com/thumbs/jazz-2025-v2.jpg",
    "gallery": ["https://cdn.example.com/gallery/img1.jpg"]
  }
}
```

**Request Body Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `title` | string | No | Updated event title | Min: 3, Max: 200 characters |
| `description` | string | No | Updated description | Min: 15, Max: 5000 characters |
| `categoryId` | UUID | No | New category | Must exist and be active |
| `eventVisibility` | string | No | Visibility change | Enum: `PUBLIC`, `PRIVATE`, `UNLISTED` |
| `eventFormat` | string | No | Format change | Enum: `IN_PERSON`, `ONLINE`, `HYBRID`, `TBA` |
| `ctaLabel` | string | No | CTA button label override | Max: 50 characters. If omitted, existing value is kept; auto-derived at publish if never set |
| `media` | object | No | Updated media | See media fields in endpoint 1 |

**Success Response JSON Sample**:

```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Basic info updated",
  "action_time": "2025-02-17T10:30:45",
  "data": { "...": "Full EventResponse object" }
}
```

**Success Response Fields**: `data` is a full [EventResponse](#a-eventresponse-full-event-object). Note that `completedStages` will now include `"BASIC_INFO"` and `currentStage` will be `"SCHEDULE"`.

**Possible Error Responses**:

| Status | Scenario |
|--------|----------|
| `401` | No or expired token |
| `403` | Not the draft owner |
| `404` | Draft or category not found |
| `422` | Validation failure on provided fields |

---

## 6. Update Draft — Schedule

**Purpose**: Sets the event's day-by-day schedule. Supports multi-day events. Each day must have a unique date in chronological order. Advances `currentStage` to `LOCATION_DETAILS`. The overall `startDateTime` and `endDateTime` on the event are derived automatically from the first and last day.

**Endpoint**: `PATCH` `/api/v1/e-events/drafts/{draftId}/schedule`

**Access Level**: 🔒 Protected (Organizer — own drafts only)

**Authentication**: Bearer Token

**Request Headers**:

| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <token>` |
| `Content-Type` | string | Yes | `application/json` |

**Path Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `draftId` | UUID | Yes | The draft to update | Must be a valid UUID |

**Request JSON Sample**:

```json
{
  "timezone": "Africa/Dar_es_Salaam",
  "days": [
    {
      "date": "2025-07-18",
      "startTime": "18:00:00",
      "endTime": "23:00:00",
      "description": "Opening Night",
      "dayOrder": 1
    },
    {
      "date": "2025-07-19",
      "startTime": "16:00:00",
      "endTime": "23:59:00",
      "description": "Main Concert Day",
      "dayOrder": 2
    }
  ]
}
```

**Request Body Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `timezone` | string | No | IANA timezone identifier | Must be a valid IANA zone ID (e.g., `Africa/Dar_es_Salaam`). Defaults to `UTC` |
| `days` | array | Yes | List of event days | Min 1 day required |
| `days[].date` | string | Yes | Date of the day | `YYYY-MM-DD` format; must not be in the past |
| `days[].startTime` | string | Yes | Day start time | `HH:mm:ss` format |
| `days[].endTime` | string | Yes | Day end time | `HH:mm:ss`; must be after `startTime` |
| `days[].description` | string | No | Optional day description | — |
| `days[].dayOrder` | integer | No | Display order | Defaults to position in array if omitted |

> **Notes**:
> - All existing days are replaced on each call. To update the schedule, resend the full days array.
> - Dates must be unique — duplicate dates in the same request are rejected with `422`.
> - Days must be provided in chronological order (sorted ascending by date).

**Success Response JSON Sample**:

```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Schedule updated",
  "action_time": "2025-02-17T10:30:45",
  "data": { "...": "Full EventResponse object" }
}
```

**Success Response Fields**: `data` is a full [EventResponse](#a-eventresponse-full-event-object). `schedule.days` will be populated, and `schedule.startDateTime` / `schedule.endDateTime` will be set from the first and last day.

**Possible Error Responses**:

| Status | Scenario |
|--------|----------|
| `401` | No or expired token |
| `403` | Not the draft owner |
| `404` | Draft not found |
| `422` | Days out of order, duplicate dates, date in the past, missing time fields |

---

## 7. Update Draft — Location

**Purpose**: Sets the physical venue and/or virtual meeting details for the event. Required fields depend on `eventFormat`. For `TBA` format, this endpoint can be called but no fields are required — the stage is automatically marked complete. Advances `currentStage` to `TICKETS`.

**Endpoint**: `PATCH` `/api/v1/e-events/drafts/{draftId}/location`

**Access Level**: 🔒 Protected (Organizer — own drafts only)

**Authentication**: Bearer Token

**Request Headers**:

| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <token>` |
| `Content-Type` | string | Yes | `application/json` |

**Path Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `draftId` | UUID | Yes | The draft to update | Must be a valid UUID |

**Request JSON Sample** (IN_PERSON):

```json
{
  "venue": {
    "name": "Mlimani City Arena",
    "address": "Sam Nujoma Road, Dar es Salaam",
    "coordinates": {
      "latitude": -6.7724,
      "longitude": 39.2083
    }
  }
}
```

**Request JSON Sample** (ONLINE):

```json
{
  "virtualDetails": {
    "meetingLink": "https://zoom.us/j/123456789",
    "meetingId": "123 456 789",
    "passcode": "jazz2025"
  }
}
```

**Request Body Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `venue` | object | Conditional | Required when format is `IN_PERSON` or `HYBRID` | — |
| `venue.name` | string | Yes (if venue) | Venue name | Max: 200 characters |
| `venue.address` | string | No | Full address | Max: 500 characters |
| `venue.coordinates.latitude` | BigDecimal | No | GPS latitude | — |
| `venue.coordinates.longitude` | BigDecimal | No | GPS longitude | — |
| `virtualDetails` | object | Conditional | Required when format is `ONLINE` or `HYBRID` | — |
| `virtualDetails.meetingLink` | string | Yes (if virtualDetails) | Meeting URL | Max: 500 characters |
| `virtualDetails.meetingId` | string | No | Platform meeting ID | Max: 100 characters |
| `virtualDetails.passcode` | string | No | Meeting passcode | Max: 100 characters |

> **Format-based rules**:
> - `IN_PERSON` → `venue.name` is required; `virtualDetails` is ignored
> - `ONLINE` → `virtualDetails.meetingLink` is required; `venue` is ignored
> - `HYBRID` → both `venue.name` and `virtualDetails.meetingLink` are required
> - `TBA` → no fields required; stage is immediately marked complete

**Success Response JSON Sample**:

```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Location updated",
  "action_time": "2025-02-17T10:30:45",
  "data": { "...": "Full EventResponse object" }
}
```

**Success Response Fields**: `data` is a full [EventResponse](#a-eventresponse-full-event-object). `completedStages` will include `"LOCATION_DETAILS"` when requirements are met and `currentStage` will advance to `"TICKETS"`.

**Possible Error Responses**:

| Status | Scenario |
|--------|----------|
| `401` | No or expired token |
| `403` | Not the draft owner |
| `404` | Draft not found |
| `422` | Missing required venue/virtual fields for the event format |

---

## 8. Update Draft — Highlights

**Purpose**: Replaces the full list of event highlights (key attendee-facing info such as age restriction, dress code, parking, etc.). Sending an empty array clears all highlights.

**Endpoint**: `PATCH` `/api/v1/e-events/drafts/{draftId}/highlights`

**Access Level**: 🔒 Protected (Organizer — own drafts only)

**Authentication**: Bearer Token

**Request Headers**:

| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <token>` |
| `Content-Type` | string | Yes | `application/json` |

**Path Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `draftId` | UUID | Yes | The draft to update | Must be a valid UUID |

**Request JSON Sample**:

```json
{
  "highlights": [
    {
      "type": "AGE_RESTRICTION",
      "title": "Age Limit",
      "value": "18+",
      "description": "This event is strictly for adults aged 18 and above."
    },
    {
      "type": "DRESS_CODE",
      "title": "Dress Code",
      "value": "Smart Casual",
      "description": "No sportswear or flip flops allowed."
    }
  ]
}
```

**Request Body Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `highlights` | array | Yes | Full list of highlights | Empty array is valid (clears existing) |
| `highlights[].type` | string | Yes | Highlight category | Enum: `AGE_RESTRICTION`, `CHECK_IN_TIME`, `PARKING`, `DRESS_CODE`, `FOOD_DRINKS`, `ACCESSIBILITY`, `REFUND_POLICY`, `WHAT_TO_BRING`, `PROHIBITED_ITEMS`, `WEATHER_INFO`, `CUSTOM` |
| `highlights[].title` | string | Yes | Display title | — |
| `highlights[].value` | string | No | Short value summary | — |
| `highlights[].description` | string | No | Longer explanation | — |

**Success Response JSON Sample**:

```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Highlights updated",
  "action_time": "2025-02-17T10:30:45",
  "data": { "...": "Full EventResponse object" }
}
```

**Success Response Fields**: `data` is a full [EventResponse](#a-eventresponse-full-event-object). `data.highlights` will reflect the new list.

**Possible Error Responses**:

| Status | Scenario |
|--------|----------|
| `401` | No or expired token |
| `403` | Not the draft owner |
| `404` | Draft not found |

---

## 9. Update Draft — FAQs

**Purpose**: Replaces the full list of frequently asked questions for the event. Sending an empty array clears all FAQs.

**Endpoint**: `PATCH` `/api/v1/e-events/drafts/{draftId}/faqs`

**Access Level**: 🔒 Protected (Organizer — own drafts only)

**Authentication**: Bearer Token

**Request Headers**:

| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <token>` |
| `Content-Type` | string | Yes | `application/json` |

**Path Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `draftId` | UUID | Yes | The draft to update | Must be a valid UUID |

**Request JSON Sample**:

```json
{
  "faqs": [
    {
      "question": "Is this event suitable for children?",
      "answer": "No. This event is strictly 18+ only.",
      "order": 1
    },
    {
      "question": "Is parking available?",
      "answer": "Yes, free parking is available on site.",
      "order": 2
    }
  ]
}
```

**Request Body Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `faqs` | array | Yes | Full list of FAQs | Empty array is valid (clears existing) |
| `faqs[].question` | string | Yes | FAQ question text | — |
| `faqs[].answer` | string | Yes | FAQ answer text | — |
| `faqs[].order` | integer | No | Display order | — |

**Success Response JSON Sample**:

```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "FAQs updated",
  "action_time": "2025-02-17T10:30:45",
  "data": { "...": "Full EventResponse object" }
}
```

**Success Response Fields**: `data` is a full [EventResponse](#a-eventresponse-full-event-object). `data.faqs` will reflect the new list.

**Possible Error Responses**: Same as Endpoint 8 (401, 403, 404).

---

## 10. Update Draft — Lineup

**Purpose**: Replaces the full event lineup. Supports both platform users (whose profile data is auto-enriched) and custom entries for external performers or speakers. Sending an empty array clears the lineup.

**Endpoint**: `PATCH` `/api/v1/e-events/drafts/{draftId}/lineup`

**Access Level**: 🔒 Protected (Organizer — own drafts only)

**Authentication**: Bearer Token

**Request Headers**:

| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <token>` |
| `Content-Type` | string | Yes | `application/json` |

**Path Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `draftId` | UUID | Yes | The draft to update | Must be a valid UUID |

**Request JSON Sample**:

```json
{
  "lineup": [
    {
      "entryType": "PLATFORM_USER",
      "userId": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
      "role": "HEADLINER",
      "performanceDay": 1,
      "performanceTime": "21:00",
      "order": 1
    },
    {
      "entryType": "CUSTOM",
      "name": "DJ Afrobeat",
      "role": "DJ",
      "title": "International Guest DJ",
      "bio": "Award-winning DJ from Lagos with 10 years of experience.",
      "image": "https://cdn.example.com/artists/dj-afrobeat.jpg",
      "performanceDay": 2,
      "performanceTime": "22:00",
      "order": 2
    }
  ]
}
```

**Request Body Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `lineup` | array | Yes | Full lineup list | Empty array is valid |
| `lineup[].entryType` | string | Yes | Entry source | Enum: `PLATFORM_USER`, `CUSTOM` |
| `lineup[].userId` | UUID | Conditional | Platform user ID | Required when `entryType=PLATFORM_USER`; must exist in the system |
| `lineup[].name` | string | Conditional | Display name | Required when `entryType=CUSTOM`; auto-set from user profile when `PLATFORM_USER` |
| `lineup[].role` | string | No | Lineup role | Enum: `HEADLINER`, `PERFORMER`, `SPEAKER`, `DJ`, `HOST`, `PANELIST`, `MODERATOR`, `GUEST` |
| `lineup[].title` | string | No | Professional title | — |
| `lineup[].bio` | string | No | Biography text | Auto-set from user profile when `PLATFORM_USER` |
| `lineup[].image` | string | No | Headshot URL | Auto-set from user profile when `PLATFORM_USER` |
| `lineup[].performanceDay` | integer | No | Day number of performance | — |
| `lineup[].performanceTime` | string | No | Performance start time (`HH:mm`) | — |
| `lineup[].order` | integer | No | Display order | — |

> **Auto-enrichment**: When `entryType=PLATFORM_USER`, the system automatically fetches and populates `name`, `bio`, and `image` from the user's profile. These are also refreshed on every `GET` of the event.

**Success Response JSON Sample**:

```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Lineup updated",
  "action_time": "2025-02-17T10:30:45",
  "data": { "...": "Full EventResponse object" }
}
```

**Success Response Fields**: `data` is a full [EventResponse](#a-eventresponse-full-event-object). `data.lineup` will contain enriched entries.

**Possible Error Responses**:

| Status | Scenario |
|--------|----------|
| `401` | No or expired token |
| `403` | Not the draft owner |
| `404` | Draft not found, or a `userId` in lineup does not exist |

---

## 11. Update Draft — Agenda

**Purpose**: Replaces the full event agenda organized by day and session. Supports both platform users and custom entries as session presenters. Sending an empty array clears the agenda.

**Endpoint**: `PATCH` `/api/v1/e-events/drafts/{draftId}/agenda`

**Access Level**: 🔒 Protected (Organizer — own drafts only)

**Authentication**: Bearer Token

**Request Headers**:

| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <token>` |
| `Content-Type` | string | Yes | `application/json` |

**Path Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `draftId` | UUID | Yes | The draft to update | Must be a valid UUID |

**Request JSON Sample**:

```json
{
  "agenda": [
    {
      "dayNumber": 1,
      "date": "2025-07-18",
      "sessions": [
        {
          "startTime": "18:00",
          "endTime": "19:00",
          "title": "Welcome & Opening Ceremony",
          "type": "CEREMONY",
          "location": "Main Stage",
          "presenterType": "PLATFORM_USER",
          "presenterId": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
        },
        {
          "startTime": "20:00",
          "endTime": "22:00",
          "title": "Headliner Performance",
          "type": "PERFORMANCE",
          "presenterType": "CUSTOM",
          "presenterName": "The Sauti Sol Band",
          "presenterTitle": "Headline Act",
          "presenterBio": "East Africa's most celebrated group."
        }
      ]
    }
  ]
}
```

**Request Body Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `agenda` | array | Yes | Full agenda list (by day) | Empty array is valid |
| `agenda[].dayNumber` | integer | Yes | Day number (1-based) | — |
| `agenda[].date` | string | No | Date string (`YYYY-MM-DD`) | — |
| `agenda[].sessions` | array | Yes | Sessions for this day | — |
| `sessions[].startTime` | string | Yes | Session start (`HH:mm`) | — |
| `sessions[].endTime` | string | Yes | Session end (`HH:mm`) | — |
| `sessions[].title` | string | Yes | Session name | — |
| `sessions[].description` | string | No | Session description | — |
| `sessions[].type` | string | No | Session type | Enum: `GENERAL`, `PERFORMANCE`, `CEREMONY`, `PANEL`, `WORKSHOP`, `NETWORKING`, `MEAL`, `BREAK` |
| `sessions[].location` | string | No | Sub-location within venue | — |
| `sessions[].presenterType` | string | No | Presenter source | Enum: `PLATFORM_USER`, `CUSTOM` |
| `sessions[].presenterId` | UUID | Conditional | Platform user ID | Required when `presenterType=PLATFORM_USER` |
| `sessions[].presenterName` | string | Conditional | Presenter name | Required when `presenterType=CUSTOM`; auto-set when `PLATFORM_USER` |
| `sessions[].presenterTitle` | string | No | Professional title | Auto-enriched for platform users |
| `sessions[].presenterBio` | string | No | Biography | Auto-enriched for platform users |
| `sessions[].presenterImage` | string | No | Image URL | Auto-enriched for platform users |

**Success Response JSON Sample**:

```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Agenda updated",
  "action_time": "2025-02-17T10:30:45",
  "data": { "...": "Full EventResponse object" }
}
```

**Success Response Fields**: `data` is a full [EventResponse](#a-eventresponse-full-event-object). `data.agenda` will contain enriched sessions.

**Possible Error Responses**:

| Status | Scenario |
|--------|----------|
| `401` | No or expired token |
| `403` | Not the draft owner |
| `404` | Draft not found, or a `presenterId` does not exist |

---

## 12. Attach Product to Event

**Purpose**: Links an existing active product from the PRODUCT domain to an event. Allowed on both `DRAFT` and `PUBLISHED` events. Marks the `LINKS` stage as completed.

**Endpoint**: `POST` `/api/v1/e-events/draft/{eventId}/products/{productId}`

**Access Level**: 🔒 Protected (Organizer — own events only)

**Authentication**: Bearer Token

**Request Headers**:

| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <token>` |

**Path Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `eventId` | UUID | Yes | The event (DRAFT or PUBLISHED) | Must be a valid UUID |
| `productId` | UUID | Yes | The product to attach | Must exist and have status `ACTIVE` |

**Success Response JSON Sample**:

```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Product attached",
  "action_time": "2025-02-17T10:30:45",
  "data": { "...": "Full EventResponse object" }
}
```

**Success Response Fields**: `data` is a full [EventResponse](#a-eventresponse-full-event-object). `data.linkedProducts` will include the newly attached product.

**Possible Error Responses**:

| Status | Scenario |
|--------|----------|
| `401` | No or expired token |
| `403` | Not the event organizer |
| `404` | Event or product not found |
| `400` | Product is not active, already attached, or event is not in DRAFT/PUBLISHED status |

---

## 13. Remove Product from Event

**Purpose**: Detaches a previously linked product from the event. Allowed on both `DRAFT` and `PUBLISHED` events.

**Endpoint**: `DELETE` `/api/v1/e-events/draft/{eventId}/products/{productId}`

**Access Level**: 🔒 Protected (Organizer — own events only)

**Authentication**: Bearer Token

**Request Headers**:

| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <token>` |

**Path Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `eventId` | UUID | Yes | The event (DRAFT or PUBLISHED) | Must be a valid UUID |
| `productId` | UUID | Yes | The product to remove | Must be a valid UUID |

**Success Response JSON Sample**:

```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Product removed",
  "action_time": "2025-02-17T10:30:45",
  "data": { "...": "Full EventResponse object" }
}
```

**Success Response Fields**: `data` is a full [EventResponse](#a-eventresponse-full-event-object).

**Possible Error Responses**:

| Status | Scenario |
|--------|----------|
| `401` | No or expired token |
| `403` | Not the event organizer |
| `404` | Event not found, or product not currently attached |

---

## 14. Attach Shop to Event

**Purpose**: Links an existing active shop from the PRODUCT domain to an event. Allowed on both `DRAFT` and `PUBLISHED` events. Marks the `LINKS` stage as completed.

**Endpoint**: `POST` `/api/v1/e-events/draft/{eventId}/shops/{shopId}`

**Access Level**: 🔒 Protected (Organizer — own events only)

**Authentication**: Bearer Token

**Request Headers**:

| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <token>` |

**Path Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `eventId` | UUID | Yes | The event (DRAFT or PUBLISHED) | Must be a valid UUID |
| `shopId` | UUID | Yes | The shop to attach | Must exist and have status `ACTIVE` |

**Success Response JSON Sample**:

```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Shop attached",
  "action_time": "2025-02-17T10:30:45",
  "data": { "...": "Full EventResponse object" }
}
```

**Success Response Fields**: `data` is a full [EventResponse](#a-eventresponse-full-event-object). `data.linkedShops` will include the newly attached shop.

**Possible Error Responses**:

| Status | Scenario |
|--------|----------|
| `401` | No or expired token |
| `403` | Not the event organizer |
| `404` | Event or shop not found |
| `400` | Shop is not active, already attached, or event is not in DRAFT/PUBLISHED status |

---

## 15. Remove Shop from Event

**Purpose**: Detaches a previously linked shop from the event. Allowed on both `DRAFT` and `PUBLISHED` events.

**Endpoint**: `DELETE` `/api/v1/e-events/draft/{eventId}/shops/{shopId}`

**Access Level**: 🔒 Protected (Organizer — own events only)

**Authentication**: Bearer Token

**Request Headers**:

| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <token>` |

**Path Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `eventId` | UUID | Yes | The event (DRAFT or PUBLISHED) | Must be a valid UUID |
| `shopId` | UUID | Yes | The shop to remove | Must be a valid UUID |

**Success Response JSON Sample**:

```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Shop removed",
  "action_time": "2025-02-17T10:30:45",
  "data": { "...": "Full EventResponse object" }
}
```

**Success Response Fields**: `data` is a full [EventResponse](#a-eventresponse-full-event-object).

**Possible Error Responses**: Same as Endpoint 13 (401, 403, 404).

---

## 16. Publish Event

**Purpose**: Transitions a draft event to `PUBLISHED` status. This is the final step of the creation workflow. The system validates all required stages are complete, performs a duplicate event check against existing published events, generates an RSA key pair used for secure ticket QR code signing, and auto-derives the `ctaLabel` from ticket pricing if the organizer has not set one.

**Endpoint**: `PATCH` `/api/v1/e-events/{eventId}/publish`

**Access Level**: 🔒 Protected (Organizer — own events only)

**Authentication**: Bearer Token

**Request Headers**:

| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <token>` |

**Path Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `eventId` | UUID | Yes | The event to publish | Must be a valid UUID |

> **Pre-publish checklist** (all must pass or the request is rejected with `422`):
> - `BASIC_INFO` stage completed ✓
> - `SCHEDULE` stage completed (at least one day, not in the past) ✓
> - `LOCATION_DETAILS` stage completed ✓
> - At least one active ticket exists for the event ✓
> - Event start date is not in the past ✓
> - No duplicate event detected with ≥85% similarity score to another organizer's public event ✓

**Success Response JSON Sample**:

```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Event published successfully",
  "action_time": "2025-02-17T10:30:45",
  "data": { "...": "Full EventResponse object" }
}
```

**Success Response Fields**: `data` is a full [EventResponse](#a-eventresponse-full-event-object). `data.status` will be `"PUBLISHED"`.

**Error Response Sample** (duplicate detected):

```json
{
  "success": false,
  "httpStatus": "BAD_REQUEST",
  "message": "This event appears to be a duplicate of 'Dar es Salaam Jazz Night' by user123. Please make the title, date, or location more distinct.",
  "action_time": "2025-02-17T10:30:45",
  "data": "This event appears to be a duplicate..."
}
```

**Possible Error Responses**:

| Status | Scenario |
|--------|----------|
| `401` | No or expired token |
| `403` | Not the event organizer |
| `404` | Event not found |
| `400` | Event is already published, or duplicate event detected |
| `422` | One or more required stages are incomplete, or no active tickets exist |
| `500` | RSA key generation failed |

---

## 17. Unpublish Event

**Purpose**: Reverts a `PUBLISHED` event back to `DRAFT` status. Only allowed if zero tickets have been sold across all ticket types. If tickets have been sold, the organizer must cancel the event instead.

**Endpoint**: `PATCH` `/api/v1/e-events/{eventId}/unpublish`

**Access Level**: 🔒 Protected (Organizer — own events only)

**Authentication**: Bearer Token

**Request Headers**:

| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <token>` |

**Path Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `eventId` | UUID | Yes | The event to unpublish | Must be a valid UUID |

**Success Response JSON Sample**:

```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Event unpublished successfully",
  "action_time": "2025-02-17T10:30:45",
  "data": { "...": "Full EventResponse object" }
}
```

**Success Response Fields**: `data` is a full [EventResponse](#a-eventresponse-full-event-object). `data.status` will be `"DRAFT"`.

**Error Response Sample** (tickets sold):

```json
{
  "success": false,
  "httpStatus": "BAD_REQUEST",
  "message": "Cannot unpublish: tickets have already been sold. Please cancel the event instead.",
  "action_time": "2025-02-17T10:30:45",
  "data": "Cannot unpublish: tickets have already been sold. Please cancel the event instead."
}
```

**Possible Error Responses**:

| Status | Scenario |
|--------|----------|
| `401` | No or expired token |
| `403` | Not the event organizer |
| `404` | Event not found |
| `400` | Event is not currently PUBLISHED, or tickets have been sold |

---

## 18. Cancel Event

**Purpose**: Cancels an event. Can be triggered from any non-terminal status (`DRAFT`, `PUBLISHED`, `HAPPENING`). This action is irreversible. If the event was published and tickets were sold, a bulk refund process is triggered.

**Endpoint**: `PATCH` `/api/v1/e-events/{eventId}/cancel`

**Access Level**: 🔒 Protected (Organizer — own events only)

**Authentication**: Bearer Token

**Request Headers**:

| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <token>` |

**Path Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `eventId` | UUID | Yes | The event to cancel | Must be a valid UUID |

**Success Response JSON Sample**:

```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Event cancelled successfully",
  "action_time": "2025-02-17T10:30:45",
  "data": { "...": "Full EventResponse object" }
}
```

**Success Response Fields**: `data` is a full [EventResponse](#a-eventresponse-full-event-object). `data.status` will be `"CANCELLED"`.

**Possible Error Responses**:

| Status | Scenario |
|--------|----------|
| `401` | No or expired token |
| `403` | Not the event organizer |
| `404` | Event not found |
| `400` | Event is already `CANCELLED` or `COMPLETED` |

---

## 19. Update Published Event — Basic Info

**Purpose**: Updates the description, media, and/or CTA label of a published event. Title, category, and format changes are blocked on published events as the slug has already been shared publicly.

**Endpoint**: `PATCH` `/api/v1/e-events/{eventId}/published/basic-info`

**Access Level**: 🔒 Protected (Organizer — own events only)

**Authentication**: Bearer Token

**Request Headers**:

| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <token>` |
| `Content-Type` | string | Yes | `application/json` |

**Path Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `eventId` | UUID | Yes | The published event to update | Must be a valid UUID |

**Request JSON Sample**:

```json
{
  "description": "Updated: The biggest jazz event in East Africa. Now featuring 35 artists across 3 stages.",
  "ctaLabel": "Get Tickets Now",
  "media": {
    "banner": "https://cdn.example.com/banners/jazz-2025-v3.jpg",
    "thumbnail": "https://cdn.example.com/thumbs/jazz-2025-v3.jpg",
    "gallery": ["https://cdn.example.com/gallery/img1.jpg", "https://cdn.example.com/gallery/img2.jpg"]
  }
}
```

**Request Body Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `description` | string | No | Updated event description | Min: 15, Max: 5000 characters |
| `ctaLabel` | string | No | CTA button label override | Max: 50 characters. If provided and non-blank → saved as-is. If omitted or null → re-derived from current ticket pricing |
| `media` | object | No | Updated media URLs | See media fields in Endpoint 1 |
| `media.banner` | string | No | Banner image URL | Max: 500 characters |
| `media.thumbnail` | string | No | Thumbnail image URL | Max: 500 characters |
| `media.gallery` | array | No | Gallery image URLs | — |

> **Blocked fields**: `title`, `categoryId`, `eventFormat`, and `eventVisibility` cannot be changed on a published event. Providing them will have no effect.

**Success Response JSON Sample**:

```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Event info updated",
  "action_time": "2025-02-17T10:30:45",
  "data": { "...": "Full EventResponse object" }
}
```

**Success Response Fields**: `data` is a full [EventResponse](#a-eventresponse-full-event-object).

**Possible Error Responses**:

| Status | Scenario |
|--------|----------|
| `401` | No or expired token |
| `403` | Not the event organizer |
| `404` | Event not found |
| `400` | Event is not in PUBLISHED status |
| `422` | Validation failure on provided fields |

---

## 20. Update Published Event — Highlights

**Purpose**: Replaces the full list of highlights for a published event. Behavior is identical to the draft equivalent — sending an empty array clears all highlights.

**Endpoint**: `PATCH` `/api/v1/e-events/{eventId}/published/highlights`

**Access Level**: 🔒 Protected (Organizer — own events only)

**Authentication**: Bearer Token

**Request Headers**:

| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <token>` |
| `Content-Type` | string | Yes | `application/json` |

**Path Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `eventId` | UUID | Yes | The published event to update | Must be a valid UUID |

**Request Body**: Same format as [Endpoint 8 — Update Draft Highlights](#8-update-draft--highlights).

**Success Response JSON Sample**:

```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Highlights updated successfully",
  "action_time": "2025-02-17T10:30:45",
  "data": { "...": "Full EventResponse object" }
}
```

**Possible Error Responses**:

| Status | Scenario |
|--------|----------|
| `401` | No or expired token |
| `403` | Not the event organizer |
| `404` | Event not found |
| `400` | Event is not PUBLISHED |

---

## 21. Update Published Event — FAQs

**Purpose**: Replaces the full list of FAQs for a published event. Sending an empty array clears all FAQs.

**Endpoint**: `PATCH` `/api/v1/e-events/{eventId}/published/faqs`

**Access Level**: 🔒 Protected (Organizer — own events only)

**Authentication**: Bearer Token

**Request Headers**:

| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <token>` |
| `Content-Type` | string | Yes | `application/json` |

**Path Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `eventId` | UUID | Yes | The published event to update | Must be a valid UUID |

**Request Body**: Same format as [Endpoint 9 — Update Draft FAQs](#9-update-draft--faqs).

**Success Response JSON Sample**:

```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "FAQs updated successfully",
  "action_time": "2025-02-17T10:30:45",
  "data": { "...": "Full EventResponse object" }
}
```

**Possible Error Responses**: Same as Endpoint 20 (401, 403, 404, 400).

---

## 22. Update Published Event — Lineup

**Purpose**: Replaces the full event lineup on a published event. Supports the same platform user enrichment as the draft equivalent.

**Endpoint**: `PATCH` `/api/v1/e-events/{eventId}/published/lineup`

**Access Level**: 🔒 Protected (Organizer — own events only)

**Authentication**: Bearer Token

**Request Headers**:

| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <token>` |
| `Content-Type` | string | Yes | `application/json` |

**Path Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `eventId` | UUID | Yes | The published event to update | Must be a valid UUID |

**Request Body**: Same format as [Endpoint 10 — Update Draft Lineup](#10-update-draft--lineup).

**Success Response JSON Sample**:

```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Lineup updated successfully",
  "action_time": "2025-02-17T10:30:45",
  "data": { "...": "Full EventResponse object" }
}
```

**Possible Error Responses**:

| Status | Scenario |
|--------|----------|
| `401` | No or expired token |
| `403` | Not the event organizer |
| `404` | Event not found, or a `userId` in lineup does not exist |
| `400` | Event is not PUBLISHED |

---

## 23. Update Published Event — Agenda

**Purpose**: Replaces the full event agenda on a published event. Supports the same platform user enrichment as the draft equivalent.

**Endpoint**: `PATCH` `/api/v1/e-events/{eventId}/published/agenda`

**Access Level**: 🔒 Protected (Organizer — own events only)

**Authentication**: Bearer Token

**Request Headers**:

| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <token>` |
| `Content-Type` | string | Yes | `application/json` |

**Path Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `eventId` | UUID | Yes | The published event to update | Must be a valid UUID |

**Request Body**: Same format as [Endpoint 11 — Update Draft Agenda](#11-update-draft--agenda).

**Success Response JSON Sample**:

```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Agenda updated successfully",
  "action_time": "2025-02-17T10:30:45",
  "data": { "...": "Full EventResponse object" }
}
```

**Possible Error Responses**:

| Status | Scenario |
|--------|----------|
| `401` | No or expired token |
| `403` | Not the event organizer |
| `404` | Event not found, or a `presenterId` does not exist |
| `400` | Event is not PUBLISHED |

---

## 24. Reveal Location (TBA → Actual)

**Purpose**: Reveals the actual location for an event that was originally published with `eventFormat=TBA`. The new format cannot be `TBA`. Required fields depend on the new format chosen.

**Endpoint**: `PATCH` `/api/v1/e-events/{eventId}/published/reveal-location`

**Access Level**: 🔒 Protected (Organizer — own events only)

**Authentication**: Bearer Token

**Request Headers**:

| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <token>` |
| `Content-Type` | string | Yes | `application/json` |

**Path Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `eventId` | UUID | Yes | The published TBA event | Must be a valid UUID |

**Request JSON Sample** (revealing as IN_PERSON):

```json
{
  "eventFormat": "IN_PERSON",
  "venue": {
    "name": "Mlimani City Arena",
    "address": "Sam Nujoma Road, Dar es Salaam",
    "coordinates": {
      "latitude": -6.7724,
      "longitude": 39.2083
    }
  }
}
```

**Request JSON Sample** (revealing as ONLINE):

```json
{
  "eventFormat": "ONLINE",
  "virtualDetails": {
    "meetingLink": "https://zoom.us/j/123456789",
    "meetingId": "123 456 789",
    "passcode": "jazz2025"
  }
}
```

**Request Body Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `eventFormat` | string | Yes | The actual event format | Enum: `IN_PERSON`, `ONLINE`, `HYBRID` — **cannot be `TBA`** |
| `venue` | object | Conditional | Physical venue details | Required when new format is `IN_PERSON` or `HYBRID` |
| `venue.name` | string | Yes (if venue) | Venue name | Max: 200 characters |
| `venue.address` | string | No | Full address | Max: 500 characters |
| `venue.coordinates.latitude` | BigDecimal | No | GPS latitude | — |
| `venue.coordinates.longitude` | BigDecimal | No | GPS longitude | — |
| `virtualDetails` | object | Conditional | Virtual meeting details | Required when new format is `ONLINE` or `HYBRID` |
| `virtualDetails.meetingLink` | string | Yes (if virtualDetails) | Meeting URL | Max: 500 characters |
| `virtualDetails.meetingId` | string | No | Platform meeting ID | Max: 100 characters |
| `virtualDetails.passcode` | string | No | Meeting passcode | Max: 100 characters |

**Success Response JSON Sample**:

```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Location revealed successfully",
  "action_time": "2025-02-17T10:30:45",
  "data": { "...": "Full EventResponse object" }
}
```

**Possible Error Responses**:

| Status | Scenario |
|--------|----------|
| `401` | No or expired token |
| `403` | Not the event organizer |
| `404` | Event not found |
| `400` | Event is not PUBLISHED, current format is not TBA, or new format is TBA |
| `422` | Missing required venue/virtual fields for the chosen format |

---

## 25. Get Event by ID

**Purpose**: Retrieves full event details. Published events are publicly accessible without authentication. Draft events can only be viewed by their organizer.

**Endpoint**: `GET` `/api/v1/e-events/{eventId}`

**Access Level**: 🌐 Public (for PUBLISHED events) | 🔒 Protected (for DRAFT events — organizer only)

**Authentication**: Bearer Token (required only for DRAFT access)

**Request Headers**:

| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Conditional | Required when accessing a DRAFT event |

**Path Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `eventId` | UUID | Yes | The event to retrieve | Must be a valid UUID |

**Success Response JSON Sample**:

```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Event retrieved successfully",
  "action_time": "2025-02-17T10:30:45",
  "data": { "...": "Full EventResponse object" }
}
```

**Success Response Fields**: `data` is a full [EventResponse](#a-eventresponse-full-event-object).

**Possible Error Responses**:

| Status | Scenario |
|--------|----------|
| `403` | Attempting to access a DRAFT event without being its organizer |
| `404` | Event not found or is soft-deleted |

---

## 26. Get My Events

**Purpose**: Returns a paginated list of all events (any status) created by the authenticated organizer.

**Endpoint**: `GET` `/api/v1/e-events/my-events`

**Access Level**: 🔒 Protected (Organizer — own events only)

**Authentication**: Bearer Token

**Request Headers**:

| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <token>` |

**Query Parameters**:

| Parameter | Type | Required | Description | Validation | Default |
|-----------|------|----------|-------------|------------|---------|
| `page` | integer | No | Page number (1-based) | Min: 1 | `1` |
| `size` | integer | No | Items per page | Min: 1 | `10` |

**Success Response JSON Sample**:

```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Events retrieved successfully",
  "action_time": "2025-02-17T10:30:45",
  "data": {
    "content": [ "[ EventSummaryResponse objects ]" ],
    "totalElements": 12,
    "totalPages": 2
  }
}
```

**Success Response Fields**: `data.content[]` contains [EventSummaryResponse](#b-eventsummaryresponse-lightweight-list-object) objects. Pagination follows [Standard Paginated Response Wrapper](#c-standard-paginated-response-wrapper).

**Possible Error Responses**: 401 — see [Shared Error: 401](#shared-error-response-examples).

---

## 27. Get My Events by Status

**Purpose**: Returns a paginated list of the authenticated organizer's events filtered by a specific status.

**Endpoint**: `GET` `/api/v1/e-events/my-events/status/{status}`

**Access Level**: 🔒 Protected (Organizer — own events only)

**Authentication**: Bearer Token

**Request Headers**:

| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <token>` |

**Path Parameters**:

| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| `status` | string | Yes | Event status filter | Enum: `DRAFT`, `PUBLISHED`, `HAPPENING`, `CANCELLED`, `COMPLETED` |

**Query Parameters**:

| Parameter | Type | Required | Description | Validation | Default |
|-----------|------|----------|-------------|------------|---------|
| `page` | integer | No | Page number (1-based) | Min: 1 | `1` |
| `size` | integer | No | Items per page | Min: 1 | `10` |

**Success Response JSON Sample**:

```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Events retrieved successfully",
  "action_time": "2025-02-17T10:30:45",
  "data": {
    "content": [ "[ EventSummaryResponse objects ]" ],
    "totalElements": 5,
    "totalPages": 1
  }
}
```

**Success Response Fields**: Same as Endpoint 26.

**Possible Error Responses**:

| Status | Scenario |
|--------|----------|
| `401` | No or expired token |
| `400` | Invalid `status` enum value |

---

## 28. Get Events Feed

**Purpose**: Returns a paginated list of all published events for the public discovery feed. Results are ordered by creation date descending. This endpoint will eventually incorporate a recommendation algorithm.

**Endpoint**: `GET` `/api/v1/e-events/events-feed`

**Access Level**: 🌐 Public

**Authentication**: None required

**Query Parameters**:

| Parameter | Type | Required | Description | Validation | Default |
|-----------|------|----------|-------------|------------|---------|
| `page` | integer | No | Page number (1-based) | Min: 1 | `1` |
| `size` | integer | No | Items per page | Min: 1 | `10` |

**Success Response JSON Sample**:

```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Events feed retrieved successfully",
  "action_time": "2025-02-17T10:30:45",
  "data": {
    "content": [ "[ EventSummaryResponse objects — PUBLISHED only ]" ],
    "totalElements": 87,
    "totalPages": 9
  }
}
```

**Success Response Fields**: `data.content[]` contains [EventSummaryResponse](#b-eventsummaryresponse-lightweight-list-object) objects (PUBLISHED status only).

**Possible Error Responses**: None expected (no authentication required).

---

## 29. Search Events

**Purpose**: Full-text search across published event titles. Results are ordered by start date ascending.

**Endpoint**: `GET` `/api/v1/e-events/search`

**Access Level**: 🌐 Public

**Authentication**: None required

**Query Parameters**:

| Parameter | Type | Required | Description | Validation | Default |
|-----------|------|----------|-------------|------------|---------|
| `query` | string | Yes | Search keyword(s) | Non-empty string | — |
| `page` | integer | No | Page number (1-based) | Min: 1 | `1` |
| `size` | integer | No | Items per page | Min: 1 | `10` |

**Success Response JSON Sample**:

```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Search results retrieved successfully",
  "action_time": "2025-02-17T10:30:45",
  "data": {
    "content": [ "[ EventSummaryResponse objects ]" ],
    "totalElements": 4,
    "totalPages": 1
  }
}
```

**Success Response Fields**: `data.content[]` contains [EventSummaryResponse](#b-eventsummaryresponse-lightweight-list-object) objects.

**Possible Error Responses**: None expected for normal queries (empty results return an empty `content` array, not a 404).

---

## 30. Filter Events by Date Range

**Purpose**: Returns published events whose schedule overlaps with the provided date range. An event is included if it starts before `endDate` AND ends after `startDate` (overlap logic, not exact range match).

**Endpoint**: `GET` `/api/v1/e-events/filter/date`

**Access Level**: 🌐 Public

**Authentication**: None required

**Query Parameters**:

| Parameter | Type | Required | Description | Validation | Default |
|-----------|------|----------|-------------|------------|---------|
| `startDate` | ZonedDateTime | Yes | Range start | ISO 8601 with offset (e.g., `2025-07-01T00:00:00+03:00`) | — |
| `endDate` | ZonedDateTime | Yes | Range end | ISO 8601 with offset; must be after `startDate` | — |
| `page` | integer | No | Page number (1-based) | Min: 1 | `1` |
| `size` | integer | No | Items per page | Min: 1 | `10` |

**Success Response JSON Sample**:

```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Filtered events retrieved successfully",
  "action_time": "2025-02-17T10:30:45",
  "data": {
    "content": [ "[ EventSummaryResponse objects ]" ],
    "totalElements": 7,
    "totalPages": 1
  }
}
```

**Possible Error Responses**:

| Status | Scenario |
|--------|----------|
| `400` | `startDate` is after `endDate` |

---

## 31. Search and Filter Events (Combined)

**Purpose**: Combines keyword search with optional date range filtering in a single call. All parameters are optional — calling with no parameters is equivalent to getting the full published feed.

**Endpoint**: `GET` `/api/v1/e-events/filter`

**Access Level**: 🌐 Public

**Authentication**: None required

**Query Parameters**:

| Parameter | Type | Required | Description | Validation | Default |
|-----------|------|----------|-------------|------------|---------|
| `query` | string | No | Title keyword search | — | — |
| `startDate` | ZonedDateTime | No | Date range start | ISO 8601 with offset | — |
| `endDate` | ZonedDateTime | No | Date range end | ISO 8601 with offset; must be after `startDate` if both provided | — |
| `page` | integer | No | Page number (1-based) | Min: 1 | `1` |
| `size` | integer | No | Items per page | Min: 1 | `10` |

**Possible Error Responses**:

| Status | Scenario |
|--------|----------|
| `400` | `startDate` is after `endDate` (when both are provided) |

---

## 32. Search My Events (Organizer)

**Purpose**: Allows an organizer to search and filter within their own events across all statuses. Supports keyword, status filter, and date range simultaneously.

**Endpoint**: `GET` `/api/v1/e-events/my-events/search`

**Access Level**: 🔒 Protected (Organizer — own events only)

**Authentication**: Bearer Token

**Request Headers**:

| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | string | Yes | `Bearer <token>` |

**Query Parameters**:

| Parameter | Type | Required | Description | Validation | Default |
|-----------|------|----------|-------------|------------|---------|
| `query` | string | No | Keyword search on title | — | — |
| `status` | string | No | Filter by status | Enum: `DRAFT`, `PUBLISHED`, `HAPPENING`, `CANCELLED`, `COMPLETED` | — |
| `startDate` | ZonedDateTime | No | Date range start | ISO 8601 with offset | — |
| `endDate` | ZonedDateTime | No | Date range end | ISO 8601 with offset; must be after `startDate` if both provided | — |
| `page` | integer | No | Page number (1-based) | Min: 1 | `1` |
| `size` | integer | No | Items per page | Min: 1 | `10` |

**Possible Error Responses**:

| Status | Scenario |
|--------|----------|
| `401` | No or expired token |
| `400` | `startDate` is after `endDate` |

---

## Data Format Standards

| Concern | Standard |
|---------|----------|
| Timestamps | ISO 8601 with timezone offset: `2025-07-18T18:00:00+03:00` |
| Dates | `YYYY-MM-DD` format: `2025-07-18` |
| Times | `HH:mm:ss` (24-hour): `18:00:00` |
| IDs | UUID v4: `3fa85f64-5717-4562-b3fc-2c963f66afa6` |
| Pagination | 1-based `page` query parameter, Spring `Page` wrapper in response |
| Prices | `BigDecimal` with 2 decimal places; `0.00` for free |
| Enums | Uppercase strings as defined (e.g., `IN_PERSON`, `PUBLISHED`) |