# Eolem Planning API — Guide for LLM Agents

You are calling an HTTP API that creates, reads, updates, and deletes training sessions ("formations") in a Google Calendar.

## Base

- **Base URL:** `https://planning.eolem.com/api/calendar.php`
- **Authentication:** header `X-API-Key: <key>` on every request
- **Content-Type:** `application/json` for POST/PUT bodies
- **OpenAPI spec:** `openapi.yaml` (at project root)

## Core domain rules

- Formations are **all-day events**. Times (e.g. `09:00 - 17:00`) are stored in the description.
- An event title is always auto-generated as `Formation - Client - Lieu`.
- A formation spanning several days produces **one continuous event**, not one per day.
- `statut` ∈ `option` | `confirmee` | `commandee`. Use `commandee` (no accent) — the API does not accept `commandée`.
- Dates are `YYYY-MM-DD`. Times are `HH:MM` (24h).
- Google Calendar's `end` is **exclusive** (dernier jour + 1). The API handles this automatically when you POST — you pass the last day inclusive.
- `date_fin` must be ≥ `date_debut`. `heure_fin` must be > `heure_debut`.

## Statut → color mapping

| statut | colorId | Color |
|--------|---------|-------|
| `option` | 5 | Yellow (Banana) |
| `confirmee` | 1 | Lavender |
| `commandee` | 11 | Red (Tomato) |

## Operations

### 1. Create a formation

```
POST /api/calendar.php
Headers: X-API-Key, Content-Type: application/json
Body:
{
  "formation": "PHP Avancé",      // required, non-empty
  "client": "Acme Corp",           // required, non-empty
  "lieu": "Lyon",                  // required, non-empty
  "statut": "commandee",           // required: option | confirmee | commandee
  "date_debut": "2026-05-04",      // required, YYYY-MM-DD
  "date_fin": "2026-05-08",        // required, YYYY-MM-DD, >= date_debut
  "heure_debut": "09:00",          // optional, default "09:00"
  "heure_fin": "17:00",            // optional, default "17:00"
  "description": "notes"           // optional, default ""
}
Response: 201
{ "created": [ { "id": "...", "summary": "...", "start": "...", "end": "..." } ] }
```

### 2. List formations in a date range

```
GET /api/calendar.php?date_start=2026-01-01&date_end=2026-12-31
Headers: X-API-Key
Response: 200
[
  {
    "id": "abc123",
    "summary": "PHP Avancé - Acme Corp - Lyon",
    "location": "Lyon",
    "description": "09:00 - 17:00\noptional notes",
    "colorId": "11",
    "start": "2026-05-04",
    "end": "2026-05-09"    // exclusive end — actual last day is 2026-05-08
  }
]
```

### 3. Get a single event

```
GET /api/calendar.php?date_start=2026-01-01&date_end=2026-12-31&id=<eventId>
Headers: X-API-Key
Response: 200 (same shape as list item, but a single object, not an array)
```

Note: `date_start` and `date_end` are still required even with `id`.

### 4. Update a formation (partial)

```
PUT /api/calendar.php?id=<eventId>
Headers: X-API-Key, Content-Type: application/json
Body (all fields optional, only provided fields are modified):
{
  "summary": "new title",
  "location": "new place",
  "description": "new notes",
  "statut": "confirmee",           // updates color
  "colorId": "1",                  // alternative to statut
  "date_debut": "2026-05-11",      // converted to Y-m-d internally
  "date_fin": "2026-05-15"         // inclusive — exclusive conversion applied automatically
}
Response: 200 (same shape as a single event)
```

### 5. Delete a formation

```
DELETE /api/calendar.php?id=<eventId>
Headers: X-API-Key
Response: 204 (no body)
```

## Error responses

All errors return JSON `{ "error": "message", "details": ... }`.

| Code | Meaning | When |
|------|---------|------|
| 400 | Bad request | Missing required query param, invalid JSON body |
| 401 | Unauthorized | Missing or wrong `X-API-Key` |
| 405 | Method not allowed | Non-GET/POST/PUT/DELETE |
| 422 | Validation error | Body fields failed validation (`details` is a map `field → message`) |
| 503 | Google not authenticated | Server-side OAuth needs redoing via the web UI |

## Validation rules (POST)

Enforced by `FormValidator`:

- `formation`, `client`, `lieu` must be non-empty after `trim`
- `statut` must be exactly one of `option`, `confirmee`, `commandee` (case-sensitive, no accents)
- `date_debut`, `date_fin` required; `date_fin >= date_debut` (string comparison works because of ISO format)
- `heure_debut`, `heure_fin` required; `heure_fin > heure_debut`

## Patterns and guidance

**Creating a formation with defaults:**
Send only the 6 required fields — `heure_debut`, `heure_fin`, `description` will use defaults.

**Listing current year:**
```
GET /api/calendar.php?date_start=2026-01-01&date_end=2026-12-31
```

**Parsing a listed event:**
- `summary.split(" - ", 3)` → `[formation, client, lieu]` (but prefer `location` field for lieu)
- `description.split("\n")[0]` → hours line (`HH:MM - HH:MM`)
- `description` after first newline → user notes (may be empty)

**Computing actual last day from listed event:**
The returned `end` is exclusive. Actual inclusive last day = `end - 1 day`. For single-day events, `start == end - 1 day` (so the `end` in the response is `start + 1`).

**Changing a statut (e.g. option → commandee):**
```
PUT /api/calendar.php?id=<id>
{ "statut": "commandee" }
```

**Moving dates:**
```
PUT /api/calendar.php?id=<id>
{ "date_debut": "2026-06-01", "date_fin": "2026-06-03" }
```
Pass `date_fin` as the inclusive last day — the API handles Google's exclusive convention.

## Do's and don'ts

✅ Use `commandee`, `confirmee`, `option` (lowercase, no accents) for `statut`.
✅ Trust the 6 required fields on POST — omit optional ones to get defaults.
✅ Treat the listed `end` as exclusive — subtract 1 day when showing to humans.
✅ Always include `date_start` and `date_end` on GET, even when filtering by `id`.

❌ Do not send `Commandée` / `Confirmée` / `Option` with accents or capitals in POST/PUT bodies.
❌ Do not expect validation errors on GET — validation only runs on POST.
❌ Do not try to create one event per day yourself — the API creates one spanning event.
❌ Do not assume weekends are skipped — they are part of the span.

## Example: import many formations

```bash
for each row in spreadsheet:
  POST /api/calendar.php
  { formation, client, lieu, statut, date_debut, date_fin }
```

A working reference implementation lives in `import-csv.php` at the project root — it reads a semicolon-delimited CSV and posts each row.
