Files
CalendarApi/SKILL.md
Michilis 41f6ae916f first commit
Made-with: Cursor
2026-02-28 02:17:55 +00:00

397 lines
12 KiB
Markdown

# Calendar & Contacts API - Agent Skill
## Purpose
This skill enables AI agents to manage calendars, events, contacts, availability, and bookings through the Calendar & Contacts REST API. The API runs on `http://localhost:3019` by default.
## Credentials
Credentials are stored in a file called `calendarcredentials.txt` in the agent's working directory or project root. If the file does not exist, create it after registration.
### Format of calendarcredentials.txt
```
BASE_URL=http://localhost:3019
EMAIL=agent@example.com
PASSWORD=securepassword123
API_KEY=<api-key-token>
ACCESS_TOKEN=<current-jwt-access-token>
REFRESH_TOKEN=<current-jwt-refresh-token>
```
- `BASE_URL` - the API server URL (default `http://localhost:3019`)
- `EMAIL` / `PASSWORD` - account credentials used for login and token refresh
- `API_KEY` - scoped API key for long-lived programmatic access (preferred for agents)
- `ACCESS_TOKEN` / `REFRESH_TOKEN` - short-lived JWT tokens from login
### Credential Priority
1. Read `calendarcredentials.txt` first.
2. If an `API_KEY` is present, use it via `X-API-Key` header for all requests.
3. If only `ACCESS_TOKEN` is available, use `Authorization: Bearer <ACCESS_TOKEN>`.
4. If the access token returns 401, call `POST /auth/refresh` with the refresh token, update the file with new tokens, and retry.
5. If no tokens exist, call `POST /auth/login` with email/password, store the returned tokens, and proceed.
## Authentication
### Two auth methods
| Method | Header | Lifetime | Best for |
|--------|--------|----------|----------|
| JWT | `Authorization: Bearer <token>` | 15 min access / 7-30 day refresh | Interactive sessions |
| API Key | `X-API-Key: <token>` | Until revoked | Agents and automation |
### Register a new account
```
POST /auth/register
Content-Type: application/json
{"email": "agent@example.com", "password": "securepassword123", "timezone": "UTC"}
```
Returns `user`, `access_token`, `refresh_token`. Save all to `calendarcredentials.txt`.
### Login
```
POST /auth/login
Content-Type: application/json
{"email": "agent@example.com", "password": "securepassword123"}
```
Returns `user`, `access_token`, `refresh_token`.
### Refresh tokens
```
POST /auth/refresh
Content-Type: application/json
{"refresh_token": "<refresh_token>"}
```
Returns new `access_token` and `refresh_token`. Update `calendarcredentials.txt`.
### Create an API key (recommended for agents)
```
POST /api-keys
Authorization: Bearer <access_token>
Content-Type: application/json
{
"name": "agent-full-access",
"scopes": {
"calendars": ["read", "write"],
"events": ["read", "write"],
"contacts": ["read", "write"],
"availability": ["read"],
"booking": ["write"]
}
}
```
Response includes a `token` field. This is the API key. It is only returned once. Save it to `calendarcredentials.txt` immediately.
### Agent bootstrap sequence
1. Check if `calendarcredentials.txt` exists.
2. If not: register, create API key, save credentials.
3. If yes: read credentials and authenticate using API key or JWT.
## API Reference
Base URL: `http://localhost:3019`
All request/response bodies are JSON. All timestamps are RFC3339 UTC. All list endpoints return `{"items": [...], "page": {"limit": N, "next_cursor": "..."}}`.
### Calendars
| Method | Endpoint | Scope | Description |
|--------|----------|-------|-------------|
| GET | `/calendars` | calendars:read | List all calendars (owned + shared) |
| POST | `/calendars` | calendars:write | Create a calendar |
| GET | `/calendars/{id}` | calendars:read | Get a calendar by ID |
| PUT | `/calendars/{id}` | calendars:write | Update name, color, is_public |
| DELETE | `/calendars/{id}` | calendars:write | Soft-delete (owner only) |
| POST | `/calendars/{id}/share` | calendars:write | Share with another user by email |
| GET | `/calendars/{id}/members` | calendars:read | List calendar members and roles |
| DELETE | `/calendars/{id}/members/{userID}` | calendars:write | Remove a member (owner only) |
#### Create calendar
```
POST /calendars
{"name": "Work", "color": "#22C55E"}
```
#### Share a calendar
```
POST /calendars/{id}/share
{"target": {"email": "other@example.com"}, "role": "editor"}
```
### Events
| Method | Endpoint | Scope | Description |
|--------|----------|-------|-------------|
| GET | `/events?start=...&end=...` | events:read | List events in time range |
| POST | `/events` | events:write | Create an event |
| GET | `/events/{id}` | events:read | Get event with reminders/attendees |
| PUT | `/events/{id}` | events:write | Update an event |
| DELETE | `/events/{id}` | events:write | Soft-delete an event |
#### List events (required: start, end)
```
GET /events?start=2026-03-01T00:00:00Z&end=2026-03-31T23:59:59Z&calendar_id=<uuid>
```
Optional filters: `calendar_id`, `search`, `tag`, `limit` (max 200), `cursor`.
Recurring events are automatically expanded into individual occurrences within the requested range. Occurrences have `is_occurrence: true` with `occurrence_start_time` / `occurrence_end_time`.
#### Create event
```
POST /events
{
"calendar_id": "<uuid>",
"title": "Team standup",
"start_time": "2026-03-01T14:00:00Z",
"end_time": "2026-03-01T14:30:00Z",
"timezone": "America/New_York",
"all_day": false,
"recurrence_rule": "FREQ=WEEKLY;BYDAY=MO,WE,FR",
"reminders": [10, 60],
"tags": ["work", "standup"]
}
```
#### Update event
```
PUT /events/{id}
{"title": "Updated title", "start_time": "2026-03-01T15:00:00Z", "end_time": "2026-03-01T16:00:00Z"}
```
### Reminders
| Method | Endpoint | Scope | Description |
|--------|----------|-------|-------------|
| POST | `/events/{id}/reminders` | events:write | Add reminders (minutes before) |
| DELETE | `/events/{id}/reminders/{reminderID}` | events:write | Remove a reminder |
```
POST /events/{id}/reminders
{"minutes_before": [5, 15, 60]}
```
### Attendees
| Method | Endpoint | Scope | Description |
|--------|----------|-------|-------------|
| POST | `/events/{id}/attendees` | events:write | Add attendees by email or user_id |
| PUT | `/events/{id}/attendees/{attendeeID}` | events:write | Update RSVP status |
| DELETE | `/events/{id}/attendees/{attendeeID}` | events:write | Remove attendee |
```
POST /events/{id}/attendees
{"attendees": [{"email": "guest@example.com"}, {"user_id": "<uuid>"}]}
```
Status values: `pending`, `accepted`, `declined`, `tentative`.
### Contacts
| Method | Endpoint | Scope | Description |
|--------|----------|-------|-------------|
| GET | `/contacts` | contacts:read | List contacts (optional: search, limit, cursor) |
| POST | `/contacts` | contacts:write | Create a contact |
| GET | `/contacts/{id}` | contacts:read | Get a contact |
| PUT | `/contacts/{id}` | contacts:write | Update a contact |
| DELETE | `/contacts/{id}` | contacts:write | Soft-delete a contact |
```
POST /contacts
{"first_name": "Jane", "last_name": "Doe", "email": "jane@example.com", "phone": "+15551234567", "company": "Acme", "notes": "Met at conference"}
```
At least one identifying field (first_name, last_name, email, or phone) is required.
### Availability
| Method | Endpoint | Scope | Description |
|--------|----------|-------|-------------|
| GET | `/availability?calendar_id=...&start=...&end=...` | availability:read | Get busy blocks for a calendar |
```
GET /availability?calendar_id=<uuid>&start=2026-03-01T00:00:00Z&end=2026-03-07T23:59:59Z
```
Returns `busy` array of `{start, end, event_id}` blocks. Includes expanded recurring event occurrences.
### Booking (public, no auth required for GET availability and POST reserve)
| Method | Endpoint | Auth | Description |
|--------|----------|------|-------------|
| POST | `/calendars/{id}/booking-link` | booking:write | Create a public booking link |
| GET | `/booking/{token}/availability?start=...&end=...` | None | Get available slots |
| POST | `/booking/{token}/reserve` | None | Reserve a time slot |
#### Create booking link
```
POST /calendars/{id}/booking-link
{
"duration_minutes": 30,
"buffer_minutes": 0,
"timezone": "America/New_York",
"working_hours": {
"mon": [{"start": "09:00", "end": "17:00"}],
"tue": [{"start": "09:00", "end": "17:00"}],
"wed": [{"start": "09:00", "end": "17:00"}],
"thu": [{"start": "09:00", "end": "17:00"}],
"fri": [{"start": "09:00", "end": "17:00"}],
"sat": [],
"sun": []
},
"active": true
}
```
#### Reserve a slot
```
POST /booking/{token}/reserve
{"name": "Visitor", "email": "visitor@example.com", "slot_start": "2026-03-03T10:00:00Z", "slot_end": "2026-03-03T10:30:00Z", "notes": "Intro call"}
```
Returns 409 CONFLICT if the slot is no longer available.
### ICS Import/Export
| Method | Endpoint | Scope | Description |
|--------|----------|-------|-------------|
| GET | `/calendars/{id}/export.ics` | calendars:read | Export calendar as ICS file |
| POST | `/calendars/import` | calendars:write | Import ICS file (multipart/form-data) |
Export returns `Content-Type: text/calendar`.
Import requires multipart form with `calendar_id` (uuid) and `file` (.ics file).
### Users
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/users/me` | Get current user profile |
| PUT | `/users/me` | Update timezone |
| DELETE | `/users/me` | Soft-delete account and all data |
### API Keys
| Method | Endpoint | Description |
|--------|----------|-------------|
| POST | `/api-keys` | Create API key with scopes |
| GET | `/api-keys` | List API keys |
| DELETE | `/api-keys/{id}` | Revoke an API key |
### Auth
| Method | Endpoint | Auth | Description |
|--------|----------|------|-------------|
| POST | `/auth/register` | None | Create account |
| POST | `/auth/login` | None | Login |
| POST | `/auth/refresh` | None | Refresh JWT tokens |
| POST | `/auth/logout` | JWT | Revoke refresh token |
| GET | `/auth/me` | JWT/Key | Get authenticated user |
## Error Handling
All errors return:
```json
{"error": "Human-readable message", "code": "MACHINE_CODE", "details": "optional"}
```
| HTTP | Code | Meaning |
|------|------|---------|
| 400 | VALIDATION_ERROR | Invalid input |
| 401 | AUTH_REQUIRED | No credentials provided |
| 401 | AUTH_INVALID | Invalid or expired token |
| 403 | FORBIDDEN | Insufficient permission or scope |
| 404 | NOT_FOUND | Resource not found |
| 409 | CONFLICT | Duplicate or slot unavailable |
| 429 | RATE_LIMITED | Too many requests |
| 500 | INTERNAL | Server error |
On 401: refresh the access token or re-login. On 403 with an API key: the key may lack required scopes. On 429: back off and retry after a delay.
## Pagination
All list endpoints use cursor-based pagination:
```
GET /events?start=...&end=...&limit=50&cursor=<opaque-string>
```
Response always includes:
```json
{
"items": [],
"page": {"limit": 50, "next_cursor": "abc123"}
}
```
When `next_cursor` is `null`, there are no more pages. To fetch the next page, pass the cursor value as the `cursor` query parameter.
## Common Agent Workflows
### Workflow: Schedule a meeting
1. `GET /calendars` - pick the target calendar.
2. `GET /availability?calendar_id=<id>&start=...&end=...` - find a free slot.
3. `POST /events` - create the event in the free slot.
4. `POST /events/{id}/attendees` - invite attendees by email.
5. `POST /events/{id}/reminders` - set reminders.
### Workflow: Find free time across calendars
1. `GET /calendars` - list all calendars.
2. For each calendar: `GET /availability?calendar_id=<id>&start=...&end=...`
3. Compute intersection of free time.
### Workflow: Set up a public booking page
1. `GET /calendars` - pick the calendar.
2. `POST /calendars/{id}/booking-link` - create the link with working hours.
3. Share the returned `public_url` or `token` with the person who needs to book.
### Workflow: Import external calendar
1. `GET /calendars` or `POST /calendars` - ensure target calendar exists.
2. `POST /calendars/import` - upload `.ics` file as multipart form data with `calendar_id`.
## Important Constraints
- Passwords must be at least 10 characters.
- Calendar names: 1-80 characters.
- Event titles: 1-140 characters.
- Colors: hex format `#RRGGBB`.
- Timezones: valid IANA timezone names (e.g., `America/New_York`, `UTC`).
- Recurrence rules: RFC 5545 RRULE format (e.g., `FREQ=WEEKLY;BYDAY=MO,WE,FR`).
- Reminder minutes_before: 0-10080 (up to 7 days).
- Event time ranges for listing: max 1 year span.
- Pagination limit: 1-200, default 50.
- Contacts require at least one of: first_name, last_name, email, phone.
- Only calendar owners can share, delete calendars, or create booking links.
- Editors can create/update/delete events. Viewers are read-only.
## OpenAPI Spec
The full OpenAPI 3.1.0 specification is available at `GET /openapi.json`. Interactive Swagger UI is at `GET /docs`.