# My Events API

An API for creating events and taking registrations, built as a workflow on the Bosbec platform. This is the starting point of the My Events example project — it is meant to be extended.

## Base flow

1. Create an event with `POST /create`. You get back an `event_id`.
2. Share the sign-up link with your guests: `/register?id=<event_id>`.
3. A guest signs up with `POST /register?id=<event_id>`, giving a name and an email address.
4. Read the event and who signed up with `GET /event?id=<event_id>`.
5. Remove a single registration, or the whole event, when you are done.

A guest is identified by their email address. The same address can only be registered once per event.

---

## Conventions

The base URL is `/api`, on the same HTTP channel that serves the pages at `/admin` and `/register`.

Every response is JSON. Successful reads return the data directly. Everything else returns:

```json
{
  "status": "...",
  "message": "..."
}
```

Branch on `status`. It is machine-readable and stable. `message` is written for people and may be reworded. The HTTP status code carries the same outcome, so read whichever suits you.

`nbr_of_spots` and `spots_taken` are integers, not strings.

Names and email addresses are trimmed before they are stored, and email comparison is case-insensitive — `Bjorn@Bosbec.com` and `bjorn@bosbec.com` are the same guest.

An event id is four characters, a hyphen, then four digits: `ZZTY-2865`.

There is no authentication. Adding it is one of the first exercises if you build on this.

---

# Endpoints

## List events

Returns every event with the number of spots taken, but without the guest lists.

```http
GET /events
```

### Query parameters

Both are optional, and `page_index` requires `page_size`. Without them, every event is returned.

| Parameter | Type | Notes |
|---|---|---|
| `page_index` | integer | Page number, zero-based |
| `page_size` | integer | Events per page |

### Response

```json
[
  {
    "id": "ZZTY-2865",
    "name": "Breakfast",
    "description": "In the kitchen",
    "start_datetime": "2026-09-08T09:00:00",
    "nbr_of_spots": 6,
    "spots_taken": 2
  },
  {
    "id": "bCiw-7865",
    "name": "Team lunch",
    "description": "Meeting room 2",
    "start_datetime": "2026-09-15T12:00:00",
    "nbr_of_spots": 5,
    "spots_taken": 1
  }
]
```

With no events, the response is an empty array rather than an error:

```json
[]
```

This endpoint has no failure cases.

---

## Get one event

Returns the event including everyone who signed up.

```http
GET /event?id=ZZTY-2865
```

### Response

```json
{
  "name": "Breakfast",
  "description": "In the kitchen",
  "start_datetime": "2026-09-08T09:00:00",
  "nbr_of_spots": 6,
  "participants": [
    { "name": "Björn Colliander", "email": "bjorn@bosbec.com" },
    { "name": "Kalle Johansson", "email": "kalle@bosbec.com" }
  ]
}
```

`participants` is always an array, including when nobody has signed up:

```json
{
  "name": "Breakfast",
  "description": "In the kitchen",
  "start_datetime": "2026-09-08T09:00:00",
  "nbr_of_spots": 6,
  "participants": []
}
```

Note that the response does **not** contain `id`. You already have it — you passed it in.

### Errors

| Status | `status` |
|---|---|
| `400` | `missing_event_id` |
| `404` | `wrong_event_id` |

---

## Create an event

```http
POST /create
```

### Request body

Only `name` is required.

| Field | Type | Notes |
|---|---|---|
| `name` | string | Required. Must contain at least one non-whitespace character |
| `description` | string | Free text. Becomes an empty string if omitted |
| `start_datetime` | string | Local time, `YYYY-MM-DDTHH:mm:ss`. Defaults to the time of creation. Not validated — a value that is not a date is stored as given |
| `nbr_of_spots` | integer | At least 1. Becomes 1 if omitted |

```json
{
  "name": "Kickoff at Bosbec",
  "description": "In the kitchen, snacks provided.",
  "start_datetime": "2026-09-20T18:00:00",
  "nbr_of_spots": 25
}
```

### Response

`201`, with the id to build the sharing link from:

```json
{
  "event_id": "ZZTY-2865"
}
```

### Errors

| Status | `status` | When |
|---|---|---|
| `400` | `event_name_empty` | `name` is missing, empty, or only whitespace |
| `400` | `event_spots_error` | `nbr_of_spots` is less than 1 |

---

## Register a guest

```http
POST /register?id=ZZTY-2865
```

### Request body

Both fields are required.

```json
{
  "name": "Björn Colliander",
  "email": "bjorn@bosbec.com"
}
```

### Response

```json
{
  "status": "success",
  "message": "Participant created"
}
```

### Errors

The checks run in this order, and the first failure is what you get back:

| Order | Check | Status | `status` |
|---|---|---|---|
| 1 | `id` in the query string | `400` | `missing_event_id` |
| 2 | The event exists | `404` | `wrong_event_id` |
| 3 | `email` present and valid | `400` | `missing_or_invalid_email` |
| 4 | Not already registered | `409` | `already_registered` |
| 5 | `name` present | `400` | `missing_name` |
| 6 | Spots left | `409` | `event_full` |

The order is deliberate: the body is validated before capacity, so a guest who forgot to type their name is told that, rather than being told the event is full.

---

## Remove a registration

Frees the spot, so `spots_taken` goes down. The guest is identified by the email address in the body; the event id goes in the query string.

```http
DELETE /register?id=ZZTY-2865
```

### Request body

```json
{
  "email": "bjorn@bosbec.com"
}
```

### Response

```json
{
  "status": "registration_deleted",
  "message": "Registration deleted"
}
```

### Errors

| Status | `status` | When |
|---|---|---|
| `400` | `missing_event_id` | `id` is missing from the query string |
| `400` | `missing_or_invalid_email` | `email` is missing or malformed |
| `404` | `wrong_event_id` | No event with that id |
| `404` | `participant_not_found` | No registration with that email on this event |

---

## Delete an event

Removes the event and its registrations. Registrations go with the event, so you do not need to clear them first. This cannot be undone.

```http
DELETE /event?id=ZZTY-2865
```

### Response

```json
{
  "status": "event_deleted",
  "message": "Event deleted"
}
```

### Errors

| Status | `status` |
|---|---|
| `400` | `missing_event_id` |
| `404` | `wrong_event_id` |

---

# Endpoints summary

| Method | Endpoint | Description |
|---|---|---|
| `GET` | `/events` | List every event, with `spots_taken` |
| `GET` | `/event?id=` | One event, including its guest list |
| `POST` | `/create` | Create an event, returns `event_id` |
| `POST` | `/register?id=` | Sign a guest up |
| `DELETE` | `/register?id=` | Remove one registration |
| `DELETE` | `/event?id=` | Delete an event and its registrations |

---

# Status values

Every value `status` can take:

| `status` | Meaning |
|---|---|
| `success` | The guest was registered |
| `event_deleted` | The event was deleted |
| `registration_deleted` | The registration was removed |
| `event_name_empty` | `name` missing, empty, or whitespace on create |
| `event_spots_error` | `nbr_of_spots` less than 1 on create |
| `missing_event_id` | `id` missing from the query string |
| `wrong_event_id` | No event with that id |
| `missing_or_invalid_email` | `email` missing or malformed |
| `missing_name` | `name` missing, empty, or whitespace |
| `already_registered` | That email is already signed up for this event |
| `event_full` | Every spot is taken |
| `participant_not_found` | No registration with that email on this event |

---

# Notes

- Events and registrations are stored as Units in two groups on the account. The only link between them is a matching `event_id` value — there is no relation type.
- `spots_taken` is a counter on the event, incremented on registration and decremented when one is removed. It is not derived from counting participants.
- `start_datetime` is not validated. If you need it to be a real date, validate it before you call `POST /create`, or add a check to the workflow.
- There is no `PUT`. Editing an existing event is one of the suggested exercises.
- The API deliberately leaves out what a production system would need: authentication, a registration deadline, a waiting list, and messaging the people who signed up.
