# KommeNicht API Documentation

A "reverse <abbr title="Répondez s'il vous plaît (French: Please respond)">RSVP</abbr>" system where attendees are assumed attending by default and only mark themselves as NOT attending.

## Base URL

```
https://kommenicht.de
```

## Authentication

No authentication required. Members are identified via:
- **Cookie**: `member_{eventSlug}={memberSlug}` (set automatically on join)
- **Header**: `X-Member-Slug: {memberSlug}` (for API-only clients)

## Error Responses

Error format depends on the `Accept` header:

### RFC 7807 Problem Details (JSON)

Send `Accept: application/problem+json` to get [RFC 7807](https://tools.ietf.org/html/rfc7807) JSON responses:

```json
{
  "type": "https://kommenicht.de/api/problems/not-found",
  "title": "Not Found",
  "status": 404,
  "detail": "Event not found"
}
```

### RFC 7807 Problem Details (XML)

Send `Accept: application/problem+xml` to get [RFC 7807](https://tools.ietf.org/html/rfc7807) XML responses:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<problem xmlns="urn:ietf:rfc:7807">
  <type>https://kommenicht.de/api/problems/not-found</type>
  <title>Not Found</title>
  <status>404</status>
  <detail>Event not found</detail>
</problem>
```

### JSON (default)

Without a problem+json/xml Accept header, you get the same Problem Details structure with `application/json`:

```json
{
  "type": "https://kommenicht.de/api/problems/not-found",
  "title": "Not Found",
  "status": 404,
  "detail": "Event not found"
}
```

| Field | Description |
|-------|-------------|
| type | URI identifying the problem type |
| title | Short human-readable summary |
| status | HTTP status code |
| detail | Human-readable explanation |
| errors | Validation errors object (for 400 responses) |

## Response Formats (Hypermedia)

The API supports multiple response formats via content negotiation. Use the `Accept` header to request your preferred format.

| Accept Header | Content-Type | Format |
|---------------|--------------|--------|
| `application/json` | `application/json` | Plain JSON (default) |
| `application/hal+json` | `application/hal+json` | HAL (Hypertext Application Language) |
| `application/vnd.siren+json` | `application/vnd.siren+json` | Siren |

### Example: GET /api/events/{slug}

<details>
<summary><strong>JSON (default)</strong></summary>

```json
{
  "slug": "abc123xyz",
  "title": "Weekly Volleyball",
  "rrule": "DTSTART:20260211T190000\\nRRULE:FREQ=WEEKLY;BYDAY=WE",
  "minAttendees": 4,
  "duration": 90,
  "location": "Sports Hall",
  "organizer": {
    "slug": "org123",
    "name": "Alice"
  },
  "members": [
    {
      "slug": "org123",
      "name": "Alice",
      "isOrganizer": true
    },
    {
      "slug": "mem456",
      "name": "Bob",
      "isOrganizer": false
    }
  ],
  "occurrences": [
    {
      "date": "2026-02-11",
      "attendingCount": 2,
      "absentMembers": []
    }
  ]
}
```
</details>

<details>
<summary><strong>HAL (application/hal+json)</strong></summary>

```json
{
  "slug": "abc123xyz",
  "title": "Weekly Volleyball",
  "rrule": "DTSTART:20260211T190000\\nRRULE:FREQ=WEEKLY;BYDAY=WE",
  "minAttendees": 4,
  "duration": 90,
  "location": "Sports Hall",
  "_links": {
    "self": {
      "href": "/api/events/abc123xyz"
    },
    "members": {
      "href": "/api/events/abc123xyz/members"
    },
    "calendar": {
      "href": "/api/cal/event/token123.ics",
      "type": "text/calendar"
    },
    "action:join": {
      "href": "/api/events/abc123xyz/members",
      "title": "Join event"
    },
    "action:update": {
      "href": "/api/events/abc123xyz",
      "title": "Update event"
    }
  },
  "_embedded": {
    "organizer": {
      "slug": "org123",
      "name": "Alice",
      "_links": {
        "self": {
          "href": "/api/members/org123"
        }
      }
    },
    "item": [
      {
        "slug": "org123",
        "name": "Alice",
        "_links": {
          "self": {
            "href": "/api/members/org123"
          }
        }
      },
      {
        "slug": "mem456",
        "name": "Bob",
        "_links": {
          "self": {
            "href": "/api/members/mem456"
          }
        }
      }
    ]
  }
}
```

HAL uses `_links` for hypermedia controls and `_embedded` for nested resources. Actions are represented as links with an `action:` prefix.
</details>

<details>
<summary><strong>Siren (application/vnd.siren+json)</strong></summary>

```json
{
  "class": [
    "event"
  ],
  "properties": {
    "slug": "abc123xyz",
    "title": "Weekly Volleyball",
    "rrule": "DTSTART:20260211T190000\\nRRULE:FREQ=WEEKLY;BYDAY=WE",
    "minAttendees": 4,
    "duration": 90,
    "location": "Sports Hall"
  },
  "entities": [
    {
      "class": [
        "member"
      ],
      "rel": [
        "organizer"
      ],
      "properties": {
        "slug": "org123",
        "name": "Alice"
      },
      "links": [
        {
          "rel": [
            "self"
          ],
          "href": "/api/members/org123"
        }
      ]
    },
    {
      "class": [
        "member"
      ],
      "rel": [
        "item"
      ],
      "properties": {
        "slug": "mem456",
        "name": "Bob"
      },
      "links": [
        {
          "rel": [
            "self"
          ],
          "href": "/api/members/mem456"
        }
      ]
    }
  ],
  "links": [
    {
      "rel": [
        "self"
      ],
      "href": "/api/events/abc123xyz"
    },
    {
      "rel": [
        "members",
        "collection"
      ],
      "href": "/api/events/abc123xyz/members"
    },
    {
      "rel": [
        "calendar"
      ],
      "href": "/api/cal/event/token123.ics",
      "type": "text/calendar"
    }
  ],
  "actions": [
    {
      "name": "join",
      "title": "Join event",
      "method": "POST",
      "href": "/api/events/abc123xyz/members",
      "type": "application/json",
      "fields": [
        {
          "name": "name",
          "type": "text"
        }
      ]
    },
    {
      "name": "update",
      "title": "Update event",
      "method": "PUT",
      "href": "/api/events/abc123xyz",
      "type": "application/json",
      "fields": [
        {
          "name": "title",
          "type": "text"
        },
        {
          "name": "minAttendees",
          "type": "number"
        }
      ]
    }
  ]
}
```

Siren provides full hypermedia support with `class`, `properties`, `entities`, `links`, and `actions`. Actions include form fields for self-documenting APIs.
</details>

### Conditional Actions

Actions in HAL and Siren responses are **conditional** based on authorization:

| Role | Available Actions |
|------|-------------------|
| Visitor (not a member) | `join` |
| Member | `mark-absent`, `remove-absence` |
| Organizer | `update`, `delete`, `mark-absent`, `remove-absence` |

## Events

### Create Event

Creates a new event with the caller as organizer.

```
POST /api/events
```

#### Request Body

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| title | string | Yes | Event title |
| rrule | string | Yes | iCal RRULE format. Format: DTSTART:YYYYMMDDTHHmmss\nRRULE:FREQ=WEEKLY;BYDAY=XX |
| minAttendees | integer | Yes | Minimum attendees for event to happen |
| duration | integer | Yes | Duration in minutes |
| organizerName | string | Yes | Name of the organizer |
| location | string | No | Event location (optional) |

#### Response

```json
{
  "event": {
    "id": 0,
    "slug": "V1StGXR8z",
    "title": "string",
    "rrule": "string",
    "minAttendees": 0,
    "duration": 0,
    "location": "string",
    "icalToken": "abc123xyz",
    "organizerId": 0,
    "createdAt": "string"
  },
  "organizer": {
    "id": 0,
    "slug": "kEVfRFTjk",
    "name": "string",
    "icalToken": "string",
    "createdAt": "string"
  }
}
```

---

### Get Event

Returns event details with members and upcoming occurrences.

```
GET /api/events/{slug}
```

#### Response

```json
{
  "event": {
    "id": 0,
    "slug": "V1StGXR8z",
    "title": "string",
    "rrule": "string",
    "minAttendees": 0,
    "duration": 0,
    "location": "string",
    "icalToken": "abc123xyz",
    "organizerId": 0,
    "createdAt": "string"
  },
  "members": [
    {
      "id": 0,
      "slug": "kEVfRFTjk",
      "name": "string",
      "icalToken": "string",
      "createdAt": "string"
    }
  ],
  "occurrences": [
    {
      "date": "2026-02-11",
      "dateString": "2026-02-11",
      "attendingCount": 0,
      "absentMembers": [
        {
          "id": 0,
          "slug": "string",
          "name": "string"
        }
      ]
    }
  ]
}
```

---

### Update Event

Updates event details. Only the organizer can update.

```
PUT /api/events/{slug}
```

#### Request Body

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| title | string | No | Event title |
| dayOfWeek | integer | No | Day of week (0=Sunday, 6=Saturday) |
| startTime | string | No | Start time (HH:mm) |
| minAttendees | integer | No | Minimum attendees |
| duration | integer | No | Duration in minutes |
| location | string | No | Event location |

#### Response

```json
{
  "event": {
    "id": 0,
    "slug": "V1StGXR8z",
    "title": "string",
    "rrule": "string",
    "minAttendees": 0,
    "duration": 0,
    "location": "string",
    "icalToken": "abc123xyz",
    "organizerId": 0,
    "createdAt": "string"
  }
}
```

---

### Delete Event

Deletes an event and all associated data. Only the organizer can delete.

```
DELETE /api/events/{slug}
```

#### Response

```json
{
  "success": true
}
```

---

## Members

### Join Event

Adds a new member to an event. Sets cookie: member_{eventSlug}={memberSlug}

```
POST /api/events/{slug}/members
```

#### Request Body

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| name | string | Yes | Member's display name |

#### Response

```json
{
  "member": {
    "id": 0,
    "slug": "kEVfRFTjk",
    "name": "string",
    "icalToken": "string",
    "createdAt": "string"
  }
}
```

---

### List Members

Returns all members of an event.

```
GET /api/events/{slug}/members
```

#### Response

```json
{
  "members": [
    {
      "id": 0,
      "slug": "kEVfRFTjk",
      "name": "string",
      "icalToken": "string",
      "createdAt": "string"
    }
  ]
}
```

---

### Update Member

Updates a member's information. Members can only update themselves.

```
PUT /api/members/{slug}
```

#### Request Body

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| name | string | No | New display name |

#### Response

```json
{
  "member": {
    "id": 0,
    "slug": "kEVfRFTjk",
    "name": "string",
    "icalToken": "string",
    "createdAt": "string"
  }
}
```

---

### Leave Event

Removes a member from an event. Members can leave themselves, or the organizer can remove anyone.

```
DELETE /api/members/{slug}
```

#### Response

```json
{
  "success": true
}
```

---

## Absences

### Get Absences

Returns who is absent for a specific event occurrence.

```
GET /api/events/{slug}/absences
```

#### Response

```json
{
  "absences": [
    {
      "id": 0,
      "memberId": 0,
      "memberName": "string",
      "memberSlug": "string",
      "occurrenceDate": "2026-02-11",
      "markedById": 0,
      "createdAt": "string"
    }
  ]
}
```

---

### Mark Absent

Marks a member as absent for a specific occurrence. Members can mark themselves absent, or the organizer can mark anyone.

```
POST /api/events/{slug}/absences
```

#### Request Body

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| memberSlug | string | Yes | Slug of member to mark absent |
| occurrenceDate | string | Yes | Date in YYYY-MM-DD format |

#### Response

```json
{
  "absence": {
    "id": 0,
    "memberId": 0,
    "memberName": "string",
    "memberSlug": "string",
    "occurrenceDate": "2026-02-11",
    "markedById": 0,
    "createdAt": "string"
  }
}
```

---

### Remove Absence

Removes an absence record, marking the member as attending again.

```
DELETE /api/events/{slug}/absences
```

#### Request Body

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| memberSlug | string | Yes | Slug of member |
| occurrenceDate | string | Yes | Date in YYYY-MM-DD format |

#### Response

```json
{
  "success": true
}
```

---

### Toggle Attendance

Toggles a member's attendance status. Returns an HTML page with confirmation.

```
GET /api/events/{slug}/toggle/{memberSlug}/{date}
```

---

## Calendar

### Event Calendar Feed

Returns an iCal feed for all event occurrences with attendance information.

```
GET /api/cal/event/{token}.ics
```

---

### Member Calendar Feed

Returns a personalized iCal feed showing the member's attendance status with toggle URLs.

```
GET /api/cal/member/{token}.ics
```

---
