Skip to content

sen-ltd/ical-api

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ical-api

A tiny PHP HTTP service that turns JSON event data into valid RFC 5545 iCalendar (.ics) feeds. Slim 4, no iCalendar dependency, the builder is ~150 lines of careful string handling because that's the whole point of the article behind this repo.

Designed as the thing you put in front of your internal calendar data when your team keeps generating .ics feeds by string concatenation and keeps hitting the same RFC 5545 edge cases:

  • CRLF line endings (not LF)
  • Line folding at 75 octets, not 75 characters
  • Text escaping for \, ;, ,, and newlines
  • UID, DTSTAMP, DTSTART, DTEND all required on every VEVENT
  • PRODID + VERSION required on the envelope

Endpoints

Method Path Purpose
POST /ical JSON body → text/calendar response
GET /ical ?url=<json-source> → fetch + convert
POST /validate iCal text → {valid, errors:[...]}
GET /health {status, version}
GET / HTML explainer

Request body

{
  "calendar": { "name": "Team", "description": "internal", "timezone": "UTC" },
  "events": [
    {
      "uid": "optional-stable-id@example.com",
      "title": "Standup",
      "start": "2026-04-20T09:00:00Z",
      "end":   "2026-04-20T09:15:00Z",
      "location": "HQ",
      "description": "daily",
      "url": "https://example.com/meeting",
      "recurrence": { "freq": "DAILY", "interval": 1, "count": 10 }
    }
  ]
}
  • title, start, end are required on every event.
  • end must be strictly greater than start (422 otherwise).
  • uid is auto-generated if not supplied.
  • recurrence.freq is one of DAILY, WEEKLY, MONTHLY. Optional interval, and exactly one of count / until.
  • Date-times accept ISO 8601 (2026-04-20T09:00:00Z or no suffix for floating), or the iCal basic form (20260420T090000Z).

Why no VTIMEZONE?

It's complicated and most consumers do the right thing with either UTC (Z-suffixed) or floating times. If you need full VTIMEZONE + TZID parameter handling, reach for sabre/vobject — this service is deliberately the 90% case.

Run with Docker

docker build -t ical-api .
docker run --rm -p 8000:8000 ical-api
curl http://localhost:8000/health

Example

curl -sS -X POST http://localhost:8000/ical \
  -H "Content-Type: application/json" \
  -d '{
    "calendar": {"name": "Team"},
    "events": [{
      "title": "Standup",
      "start": "2026-04-20T09:00:00Z",
      "end":   "2026-04-20T09:15:00Z",
      "location": "HQ"
    }]
  }'

Response:

BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//SEN LLC//ical-api 1.0//EN
CALSCALE:GREGORIAN
METHOD:PUBLISH
X-WR-CALNAME:Team
BEGIN:VEVENT
UID:...@ical-api
DTSTAMP:20260415T120000Z
DTSTART:20260420T090000Z
DTEND:20260420T091500Z
SUMMARY:Standup
LOCATION:HQ
END:VEVENT
END:VCALENDAR

(with CRLF line endings — verify with curl ... | xxd | head and you'll see 0d 0a after every line).

Validate your own feeds

curl -sS -X POST http://localhost:8000/validate \
  -H "Content-Type: text/plain" \
  --data-binary @my-calendar.ics

Returns {"valid": true, "errors": []} or a list of structural errors with line numbers.

Tests

composer install
./vendor/bin/phpunit --no-coverage

License

MIT. See LICENSE.

Links

About

A PHP 8.2 + Slim 4 HTTP service that turns JSON event data into valid RFC 5545 iCalendar (.ics) feeds. POST events, get text/calendar back.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors