---
name: rosterly-api
description: >-
  Integrates with the Q-Rosterly Roster & Calendar HTTP API (events, rosters,
  users, public holidays, types). Use when building apps, scripts, bots, MCP
  tools, or automations against Q-Rosterly; when the user mentions Rosterly API,
  X-API-Key, calendar events, roster entries, or /api endpoints; or when
  implementing CRUD against https://rosterly.quickstopsolution.com/api.
---

# Q-Rosterly API — Agent Integration Skill

Feed this file to an AI coding agent to integrate with Q-Rosterly.
Human docs: https://rosterly.quickstopsolution.com/api/docs
This skill URL: https://rosterly.quickstopsolution.com/api/docs/agent-skill.md

================================================================================
0. QUICK START (do this first)
================================================================================

1. Ask the user for their API key (created in Settings → API Keys while signed in).
2. Never hardcode keys. Use env vars (e.g. ROSTERLY_API_KEY).
3. Prefer header auth: X-API-Key
4. All successful JSON bodies look like: { "success": true, ... }
5. All errors look like: { "success": false, "error": "..." }
6. Base URL for this deployment: https://rosterly.quickstopsolution.com/api

Minimal probe (verify key + connectivity):

  curl -sS -H "X-API-Key: $ROSTERLY_API_KEY" \
    "https://rosterly.quickstopsolution.com/api/users"

Expected: { "success": true, "data": [ { "id", "full_name", ... } ] }

================================================================================
1. WHEN TO USE THIS SKILL
================================================================================

Use this skill whenever you need to:
- List / create / update / delete calendar events
- List / create rosters or add roster day entries
- Look up staff (users), event types, roster types, or staff shifts
- Manage public holidays
- Build a client, webhook consumer, CLI, Zapier-like automation, or agent tool
  that talks to Q-Rosterly

Do NOT invent endpoints. Only use the routes listed below.

================================================================================
2. AUTHENTICATION
================================================================================

Every data endpoint requires an API key (except this public docs HTML page and
this skill file).

Preferred (header):
  X-API-Key: <your-api-key>

Also accepted (query — avoid in logs/URLs when possible):
  ?api_key=<your-api-key>

CORS: browser clients may call the API cross-origin. Send X-API-Key.
Preflight allows: Content-Type, X-API-Key, Authorization, If-None-Match,
If-Modified-Since.

Missing / invalid key → 401
  { "success": false, "error": "API key is required" }
  { "success": false, "error": "Invalid or inactive API key" }

================================================================================
3. PERMISSIONS
================================================================================

Keys are scoped. If a permission is missing → 403
  { "success": false, "error": "Insufficient permissions" }

| Permission     | Allows                                              |
|----------------|-----------------------------------------------------|
| events:read    | GET /events, GET /events/:id                        |
| events:write   | POST/PUT/DELETE /events                             |
| rosters:read   | GET /rosters, GET /rosters/:id                      |
| rosters:write  | POST /rosters, POST /rosters/:id/entries            |
| users:write    | POST /users                                         |
| types:write    | POST/PUT/DELETE /public-holidays                    |
| all            | Full access                                         |

Notes:
- GET /users, GET /public-holidays, GET /event-types, GET /roster-types,
  GET /shifts require a valid API key but do not enforce a named read
  permission in the server today. Still request least-privilege keys from users.
- Event types, roster types, and staff shifts are READ-ONLY via API.
  Creating/editing them returns 403 — admins manage those in Settings UI.

================================================================================
4. GLOBAL CONVENTIONS
================================================================================

## 4.1 Response envelope
Success list/get:
  { "success": true, "data": <object|array> }

Success create (common):
  { "success": true, "id": <number> }
  (some creates also return data — prefer checking success + id)

Errors:
  400 validation, 401 auth, 403 permission, 404 not found, 429 rate limit, 500 server

## 4.2 Rate limits
10 requests / minute / API key.
Headers: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset
On exceed: 429 with retryAfter (seconds). Back off and retry.

## 4.3 Caching (GET)
Responses may include ETag / Last-Modified.
Send If-None-Match / If-Modified-Since to get 304 Not Modified (empty body).
Use this for polling to stay under rate limits.

## 4.4 Date & time (critical)
Storage & response fields start_datetime / end_datetime:
  UTC naive MySQL strings: "YYYY-MM-DD HH:MM:SS" (no Z, no offset)

When SENDING datetimes:
  - Prefer ISO-8601 WITH offset: "2026-03-15T09:00:00+08:00" or "...Z"
    → converted to UTC for storage
  - Naive values (no offset) are interpreted as wall-clock in APP_TIMEZONE
    (default Asia/Kuala_Lumpur, UTC+8)
  - ALWAYS send an explicit offset in integrations to avoid ambiguity

Date-only params (start/end filters, holiday dates, roster entry_date):
  "YYYY-MM-DD"

## 4.5 Markdown descriptions (events)
Event description accepts Markdown (or pasted <pre> blocks normalized to code).
Responses include:
  - description       (source markdown)
  - description_html  (safe rendered HTML)

## 4.6 IDs
All IDs are positive integers. Arrays of IDs (e.g. attendees) must be integer arrays.

================================================================================
5. ENDPOINT REFERENCE
================================================================================

Base: https://rosterly.quickstopsolution.com/api

----------------------------------------
5.1 Events
----------------------------------------

GET https://rosterly.quickstopsolution.com/api/events
  Permission: events:read
  Required query: start, end  (YYYY-MM-DD or datetime)
  Optional: event_type_id, staff_id
  Overlap rule: returns events where start_datetime <= end AND end_datetime >= start
  Response data[] fields include:
    id, title, description, description_html, location, event_type_id,
    event_type_name, event_type_color, start_datetime, end_datetime, is_all_day,
    attendees[{ id, full_name, email, profile_picture }], ...

GET https://rosterly.quickstopsolution.com/api/events/:id
  Permission: events:read

POST https://rosterly.quickstopsolution.com/api/events
  Permission: events:write
  Content-Type: application/json
  Required: title, start_datetime, end_datetime
  Optional: description, location, event_type_id, is_all_day (bool), attendees (int[])
  Example body:
  {
    "title": "Team Meeting",
    "description": "## Agenda\n- Sync",
    "location": "Room A",
    "event_type_id": 1,
    "start_datetime": "2026-03-15T09:00:00+08:00",
    "end_datetime": "2026-03-15T10:00:00+08:00",
    "is_all_day": false,
    "attendees": [3, 7]
  }

PUT https://rosterly.quickstopsolution.com/api/events/:id
  Permission: events:write
  Partial update — send only fields to change.
  If attendees is sent, it replaces the attendee set.

DELETE https://rosterly.quickstopsolution.com/api/events/:id
  Permission: events:write

----------------------------------------
5.2 Rosters
----------------------------------------

GET https://rosterly.quickstopsolution.com/api/rosters
  Permission: rosters:read
  Required query: start, end (YYYY-MM-DD) — filtered by roster year/month
  Optional: roster_type_id, staff_id

POST https://rosterly.quickstopsolution.com/api/rosters
  Permission: rosters:write
  Required: name, roster_type_id, year, month
  Optional: notes
  Unique per (roster_type_id, year, month)

GET https://rosterly.quickstopsolution.com/api/rosters/:id
  Permission: rosters:read
  Optional query: staff_id
  Returns roster + entries (and shifts-by-date map when staff_id set)

POST https://rosterly.quickstopsolution.com/api/rosters/:id/entries
  Permission: rosters:write
  Required: staff_id, entry_date (YYYY-MM-DD)
  Optional: shift_id, notes

----------------------------------------
5.3 Users (staff)
----------------------------------------

GET https://rosterly.quickstopsolution.com/api/users
  Lists active non-super_admin users:
    id, full_name, username, email, phone, designation, profile_picture

POST https://rosterly.quickstopsolution.com/api/users
  Permission: users:write
  Required: username, email, password, full_name
  Optional: designation, phone
  Creates role=user

Alias: GET https://rosterly.quickstopsolution.com/api/staff → redirects to /api/users

----------------------------------------
5.4 Public holidays
----------------------------------------

GET https://rosterly.quickstopsolution.com/api/public-holidays
  Optional query: year
GET https://rosterly.quickstopsolution.com/api/public-holidays/:id

POST https://rosterly.quickstopsolution.com/api/public-holidays
  Permission: types:write
  Required: title, start_date, end_date (YYYY-MM-DD)
  Optional: is_custom (bool)

PUT https://rosterly.quickstopsolution.com/api/public-holidays/:id
  Permission: types:write
  At least one of: title, start_date, end_date, is_custom

DELETE https://rosterly.quickstopsolution.com/api/public-holidays/:id
  Permission: types:write

----------------------------------------
5.5 Catalog types (read-only via API)
----------------------------------------

GET https://rosterly.quickstopsolution.com/api/event-types
GET https://rosterly.quickstopsolution.com/api/roster-types
GET https://rosterly.quickstopsolution.com/api/shifts

POST/PUT/DELETE on these paths → 403 (Settings-only for admins).

----------------------------------------
5.6 Meta / public (no API key)
----------------------------------------

GET https://rosterly.quickstopsolution.com/api/            → JSON machine-readable overview
GET https://rosterly.quickstopsolution.com/api/docs        → HTML documentation (public, no login)
GET https://rosterly.quickstopsolution.com/api/docs/agent-skill.md → this file

================================================================================
6. AGENT WORKFLOWS (preferred patterns)
================================================================================

## A. Create a calendar event for staff
1. GET /users → resolve names → IDs
2. GET /event-types → pick event_type_id (optional)
3. POST /events with explicit-offset datetimes + attendees[]
4. Confirm via GET /events/:id or GET /events?start=&end=

## B. Schedule a roster day
1. GET /roster-types → roster_type_id
2. GET /rosters?start=&end= → find existing month roster, or POST /rosters
3. GET /shifts → shift_id
4. GET /users → staff_id
5. POST /rosters/:id/entries

## C. Sync / poll efficiently
1. First GET stores ETag / Last-Modified
2. Subsequent GETs send If-None-Match / If-Modified-Since
3. On 304: keep local cache; on 200: replace
4. Stay under 10 req/min; batch date ranges instead of per-day calls

## D. Building a typed client
Suggested helper:

  async function rosterly(path, { method = 'GET', query, body } = {}) {
    const url = new URL(path, 'https://rosterly.quickstopsolution.com/api/');
    if (query) Object.entries(query).forEach(([k, v]) => {
      if (v != null) url.searchParams.set(k, String(v));
    });
    const res = await fetch(url, {
      method,
      headers: {
        'X-API-Key': process.env.ROSTERLY_API_KEY,
        ...(body ? { 'Content-Type': 'application/json' } : {})
      },
      body: body ? JSON.stringify(body) : undefined
    });
    const json = await res.json().catch(() => ({}));
    if (!res.ok || json.success === false) {
      const err = new Error(json.error || res.statusText);
      err.status = res.status;
      err.payload = json;
      throw err;
    }
    return json;
  }

================================================================================
7. HARD RULES FOR AGENTS
================================================================================

- NEVER commit API keys or put them in client-side public repos without user consent.
- NEVER invent fields (e.g. recurrence, webhooks) — not supported unless listed here.
- NEVER try to create event-types / roster-types / shifts via API — Settings only.
- ALWAYS send Content-Type: application/json on POST/PUT with bodies.
- ALWAYS include start AND end when listing events or rosters.
- ALWAYS prefer timezone-aware ISO datetimes on write.
- On 429: sleep retryAfter seconds, then retry once; then surface to user.
- On 403: tell the user which permission is missing (from this table).
- Treat attendees on PUT as full replacement when provided.
- profile_picture values may be relative paths — prefix with the site origin if displaying.

================================================================================
8. COPY-PASTE EXAMPLES
================================================================================

# List March 2026 events
curl -sS -H "X-API-Key: $ROSTERLY_API_KEY" \
  "https://rosterly.quickstopsolution.com/api/events?start=2026-03-01&end=2026-03-31"

# Create event
curl -sS -X POST -H "X-API-Key: $ROSTERLY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Case Conference",
    "start_datetime": "2026-03-20T14:00:00+08:00",
    "end_datetime": "2026-03-20T15:00:00+08:00",
    "attendees": [1, 2]
  }' \
  "https://rosterly.quickstopsolution.com/api/events"

# List users
curl -sS -H "X-API-Key: $ROSTERLY_API_KEY" "https://rosterly.quickstopsolution.com/api/users"

# Add roster entry
curl -sS -X POST -H "X-API-Key: $ROSTERLY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"staff_id":1,"entry_date":"2026-03-21","shift_id":2}' \
  "https://rosterly.quickstopsolution.com/api/rosters/10/entries"

================================================================================
9. HOW TO INSTALL THIS AS A CURSOR SKILL
================================================================================

1. Download this file (https://rosterly.quickstopsolution.com/api/docs/agent-skill.md)
2. Create folder: .cursor/skills/rosterly-api/
3. Save as SKILL.md (same content; markdown frontmatter already present)
   OR keep as .md and @-mention / attach it in chat
4. In Cursor Agent chat: attach the file or say "follow the rosterly-api skill"
5. Provide ROSTERLY_API_KEY (and confirm base URL if non-default)

For one-shot prompts without installing:
  "Using the attached Q-Rosterly API skill, build a Node script that lists
   this week's events and prints attendee names."

================================================================================
END OF SKILL — prefer https://rosterly.quickstopsolution.com/api/docs for human-readable examples
================================================================================
