# Notifier API Guide (Operator + Agent)

This is the single source of truth for API usage in this repo. It is written for humans and AI agents.

---

## Non-negotiables (agents)

- All notification titles and messages must be Spanish (Mexico). Do not send English to users.
- Use timezone `America/Mexico_City` unless the user explicitly requests a different timezone.
- Never guess missing data (amounts, dates, names, account numbers). Ask first.
- Upload images first and verify the file exists on disk before creating notifications.
- Follow-ups do not inherit images. Every follow-up must include `image_path` if it needs an image.
- Always verify creation with `GET /notifications/{id}`.

If you are using `notifications-setup/`, generate a preview HTML file and get user approval before creating any notification.

---

## Base URL and auth

Production:
- `https://www.tonki.dev/notifier/api`

Auth header:
- `X-API-Token: <auth.api_token from config.php>`

Shell setup:
```bash
API_TOKEN="$(php -r '$config=require "config.php"; echo $config["auth"]["api_token"];')"
BASE="https://www.tonki.dev/notifier/api"
```

---

## Response format

Success:
```json
{ "success": true, "data": { ... } }
```

Error:
```json
{ "success": false, "error": "Message" }
```

---

## Common rules

- `timezone` must be an IANA name (example: `America/Mexico_City`).
- Dates sent to the API are interpreted in the notification timezone and stored as UTC.
- Recommended datetime format: `YYYY-MM-DD HH:MM` (24h).
- `image_path` is the relative path returned by `/api/upload`.
  - Public URL: `/notifier/uploads/{image_path}`
- `image_path` must exist on disk (API rejects missing or zero-byte files).
- To remove an image on update, send `image_path` as an empty string.
- Test sends (`/api/test-message`, `/api/notifications/{id}/test`) do not create history records.

---

## Uploads (mandatory verification)

### POST /upload

Form-data:
- `image` (file)

Upload:
```bash
curl -s -X POST "$BASE/upload" \
  -H "X-API-Token: $API_TOKEN" \
  -F "image=@/absolute/path/to/image.png"
```

Response:
```json
{ "success": true, "path": "2026/01/filename.png" }
```

Verify on disk (required):
```bash
ls -la /workspace/notifier/uploads/2026/01/filename.png
```

If the file is not there, stop and re-upload. Do not create notifications with an unverified path.

---

## Categories

### GET /categories
List all categories.

```bash
curl -s "$BASE/categories" -H "X-API-Token: $API_TOKEN"
```

### GET /categories/{id}
Get a single category.

### POST /categories
Create a category.

```bash
curl -s -X POST "$BASE/categories" \
  -H "Content-Type: application/json" \
  -H "X-API-Token: $API_TOKEN" \
  -d '{"name":"Servidores","color":"#0f172a","icon":"server"}'
```

### PUT /categories/{id}
Update a category.

### DELETE /categories/{id}
Delete a category (notifications become uncategorized).

---

## Notifications

### GET /notifications
Optional filters:
- `category_id`
- `status` (`active`, `paused`, `completed`)
- `search` (matches title or message)

```bash
curl -s "$BASE/notifications?status=active" -H "X-API-Token: $API_TOKEN"
```

### GET /notifications/{id}
Returns full details including `dates`, `recurrence`, and `subsequents`.

### POST /notifications
Create a notification (dates or recurrence).

#### Dates example (Spanish content)
```json
{
  "title": "Pago de mantenimiento",
  "message": "*Pago de mantenimiento*\n\nPor favor realizar el pago hoy.",
  "context": "Imagen: mantenimiento.png\nCreado via API",
  "category_id": 113,
  "timezone": "America/Mexico_City",
  "image_path": "2026/01/example.png",
  "schedule_type": "dates",
  "yearly_repeat": false,
  "dates": [
    "2026-02-01 09:00"
  ],
  "subsequents": [
    {
      "offset_minutes": 0,
      "offset_hours": 0,
      "offset_days": 1,
      "message": "*Pago de mantenimiento*\n\nYa quedo?",
      "sort_order": 0,
      "image_path": "2026/01/example.png"
    }
  ]
}
```

#### Recurrence example (monthly with weekend adjust)
```json
{
  "title": "Pago mensual",
  "message": "*Pago mensual*\n\nRecordatorio de pago.",
  "context": "Creado via API",
  "category_id": 112,
  "timezone": "America/Mexico_City",
  "schedule_type": "recurrence",
  "recurrence": {
    "type": "monthly_date",
    "day_of_month": 15,
    "time_of_day": "09:00",
    "weekend_adjust": "previous_weekday"
  },
  "subsequents": [
    {
      "offset_minutes": 0,
      "offset_hours": 0,
      "offset_days": 3,
      "message": "*Pago mensual*\n\nRecordatorio de seguimiento.",
      "sort_order": 0,
      "image_path": "2026/01/example.png"
    }
  ]
}
```

Create request:
```bash
curl -s -X POST "$BASE/notifications" \
  -H "Content-Type: application/json" \
  -H "X-API-Token: $API_TOKEN" \
  -d @- <<'JSON'
{ ... }
JSON
```

### PUT /notifications/{id}
Update a notification. Send the full payload (same shape as POST).

### DELETE /notifications/{id}
Delete a notification and its related records.

### POST /notifications/{id}/pause
Pause a notification (stops future jobs).

### POST /notifications/{id}/resume
Resume a paused notification.

### GET /notifications/{id}/preview
Preview the next occurrences for a saved recurrence notification.

### POST /notifications/{id}/test
Send the saved notification to the test chat.

---

## Recurrence rules

`recurrence.type` must be one of:
- `specific_months`
- `monthly_date`
- `monthly_weekday`
- `weekly`
- `daily`

Common field:
- `time_of_day` (HH:MM, 24h)

Type-specific fields:

- `specific_months`:
  - `months`: array of ints (1-12)
  - `day_of_month`: 1-31
- `monthly_date`:
  - `day_of_month`: 1-31
  - optional `weekend_adjust`: `previous_weekday`
- `monthly_weekday`:
  - `week_number`: `1`, `2`, `3`, `4`, or `last`
  - `weekday`: 0-6 (0=Sunday)
- `weekly`:
  - `weekdays`: array of ints (0-6)
- `daily`:
  - `interval_days`: 1+
  - `anchor_date`: `YYYY-MM-DD`

---

## Preview (before saving)

### POST /preview
Returns the next 4 occurrences from a recurrence payload.

```bash
curl -s -X POST "$BASE/preview" \
  -H "Content-Type: application/json" \
  -H "X-API-Token: $API_TOKEN" \
  -d @- <<'JSON'
{
  "schedule_type": "recurrence",
  "timezone": "America/Mexico_City",
  "recurrence": {
    "type": "weekly",
    "weekdays": [1, 3, 5],
    "time_of_day": "09:00"
  }
}
JSON
```

---

## Test message (unsaved)

### POST /test-message
Send a one-off test message to the test chat.

If `image_path` is provided, it must exist on disk or the request fails.

```bash
curl -s -X POST "$BASE/test-message" \
  -H "Content-Type: application/json" \
  -H "X-API-Token: $API_TOKEN" \
  -d '{"message":"Mensaje de prueba","image_path":"2026/01/example.png"}'
```

---

## History

### GET /history
Optional query params:
- `page` (default 1)
- `limit` (default 50, max 100)
- `notification_id` (filter by notification)

```bash
curl -s "$BASE/history?page=1&limit=50" -H "X-API-Token: $API_TOKEN"
```

### GET /history/{notificationId}
History filtered by notification id.

---

## Jobs (debug/admin)

### GET /jobs
Returns job stats by status.

### GET /jobs?status=pending,retrying,failed
Returns the most recent jobs matching those statuses.

### POST /jobs/{id}/retry
Retry a failed or pending job immediately.

---

## End-to-end example (category + upload + notification)

This is a full example you can copy and adapt. Replace values as needed.

1) Get category ID (or create it):
```bash
curl -s "$BASE/categories" -H "X-API-Token: $API_TOKEN"
```

If missing:
```bash
curl -s -X POST "$BASE/categories" \
  -H "Content-Type: application/json" \
  -H "X-API-Token: $API_TOKEN" \
  -d '{"name":"Servidores","color":"#0f172a","icon":"server"}'
```

2) Upload image and verify:
```bash
curl -s -X POST "$BASE/upload" \
  -H "X-API-Token: $API_TOKEN" \
  -F "image=@/absolute/path/to/servidor.png"

ls -la /workspace/notifier/uploads/2026/01/filename.png
```

3) Create a recurrence notification:
```bash
curl -s -X POST "$BASE/notifications" \
  -H "Content-Type: application/json" \
  -H "X-API-Token: $API_TOKEN" \
  -d @- <<'JSON'
{
  "title": "Renovar VPS",
  "message": "*Renovar VPS*\n\nFaltan 5 dias para renovar. Por favor renovar para evitar corte.",
  "timezone": "America/Mexico_City",
  "category_id": 109,
  "image_path": "2026/01/filename.png",
  "schedule_type": "recurrence",
  "recurrence": {
    "type": "monthly_date",
    "day_of_month": 1,
    "time_of_day": "09:00"
  },
  "subsequents": [
    {
      "offset_minutes": 0,
      "offset_hours": 0,
      "offset_days": 1,
      "message": "*Renovar VPS*\n\nQuedan 4 dias. Por favor renovar.",
      "sort_order": 0,
      "image_path": "2026/01/filename.png"
    },
    {
      "offset_minutes": 0,
      "offset_hours": 0,
      "offset_days": 2,
      "message": "*Renovar VPS*\n\nQuedan 3 dias. Por favor renovar.",
      "sort_order": 1,
      "image_path": "2026/01/filename.png"
    }
  ]
}
JSON
```

4) Verify:
```bash
curl -s "$BASE/notifications/ID" -H "X-API-Token: $API_TOKEN"
```

---

## UI parity notes

The UI uses the same endpoints above:
- Categories page maps to `/categories`.
- Notifications page maps to `/notifications`, plus pause/resume/test.
- Notification editor uses `/upload` and `/preview`.
- History page uses `/history` and `/jobs`.

Non-API flows:
- Login and session management are UI-only.
- Confirmation links (from Telegram) are handled by `/confirm/{token}/` page, not the API.
