- Config: try ENV_FILE, .env, ../.env for loading; trim trailing slash from BaseURL - Log BASE_URL at server startup for verification - .env.example: document BASE_URL - Tasks, projects, tags, migrations and related API/handlers Made-with: Cursor
Calendar & Contacts API
A production-grade REST API for calendar management, event scheduling, contacts, availability queries, and public booking links. Built with Go, PostgreSQL, and designed for human users, AI agents, and programmatic automation.
Features
- Calendars - Create, share, and manage multiple calendars with role-based access (owner/editor/viewer)
- Events - Full CRUD with recurring events (RFC 5545 RRULE), reminders, attendees, tags, and attachments
- Contacts - Personal contact management with search
- Availability - Query busy/free time across calendars
- Booking Links - Public scheduling pages with configurable working hours, duration, and buffer time
- ICS Import/Export - Full RFC 5545 iCalendar support with VALARM, ATTENDEE, TZID, all-day events
- Public iCal Feeds - Make calendars public and subscribe via iCal URL in any calendar app
- iCal URL Import - Import events from any external iCal feed URL
- Dual Auth - JWT tokens for interactive use, scoped API keys for agents and automation
- Background Jobs - Reminder notifications via Redis + Asynq (optional)
Tech Stack
| Component | Technology |
|---|---|
| Language | Go 1.24 |
| Router | Chi |
| Database | PostgreSQL 15+ |
| Query Layer | sqlc |
| Migrations | golang-migrate |
| Auth | JWT (golang-jwt/jwt/v5) + bcrypt |
| Recurrence | rrule-go (RFC 5545) |
| Background Jobs | Asynq (Redis) |
| API Docs | OpenAPI 3.1.0 + Swagger UI |
Quick Start
Prerequisites
- Go 1.24+
- PostgreSQL 15+
- Redis (optional, for background reminder jobs)
Setup
-
Clone the repository and navigate to the project directory.
-
Copy the environment file and configure it:
cp .env.example .env
- Edit
.envwith your database credentials:
DATABASE_URL=postgres://calendarapi:password@localhost:5432/calendarapi?sslmode=disable
JWT_SECRET=your-secret-key-change-me
SERVER_PORT=3019
ENV=development
# REDIS_ADDR=localhost:6379 # uncomment to enable background jobs
- Create the database:
createdb calendarapi
- Run the server (migrations run automatically on startup):
go run cmd/server/main.go
The server starts on http://localhost:3019. Swagger UI is available at http://localhost:3019/docs.
API Documentation
| Resource | URL |
|---|---|
| Swagger UI | http://localhost:3019/docs |
| OpenAPI Spec | http://localhost:3019/openapi.json |
| LLM Reference | llms.txt |
| Agent Skill | SKILL.md |
Authentication
JWT (for users)
Register or login to receive an access token (15 min) and refresh token:
# Register
curl -X POST http://localhost:3019/auth/register \
-H "Content-Type: application/json" \
-d '{"email": "user@example.com", "password": "securepassword123", "timezone": "UTC"}'
# Login
curl -X POST http://localhost:3019/auth/login \
-H "Content-Type: application/json" \
-d '{"email": "user@example.com", "password": "securepassword123"}'
Use the access token in subsequent requests:
Authorization: Bearer <access_token>
API Keys (for agents)
Create a scoped API key for long-lived programmatic access:
curl -X POST http://localhost:3019/api-keys \
-H "Authorization: Bearer <access_token>" \
-H "Content-Type: application/json" \
-d '{
"name": "my-agent",
"scopes": {
"calendars": ["read", "write"],
"events": ["read", "write"],
"contacts": ["read", "write"],
"availability": ["read"],
"booking": ["write"]
}
}'
Use the returned token in subsequent requests:
X-API-Key: <token>
API Endpoints
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 |
Yes | Revoke refresh token |
| GET | /auth/me |
Yes | Get current user |
Users
| Method | Endpoint | Description |
|---|---|---|
| GET | /users/me |
Get profile |
| PUT | /users/me |
Update profile |
| DELETE | /users/me |
Delete account |
API Keys
| Method | Endpoint | Description |
|---|---|---|
| POST | /api-keys |
Create API key |
| GET | /api-keys |
List API keys |
| DELETE | /api-keys/{id} |
Revoke API key |
Calendars
| Method | Endpoint | Scope | Description |
|---|---|---|---|
| GET | /calendars |
calendars:read | List calendars |
| POST | /calendars |
calendars:write | Create calendar |
| GET | /calendars/{id} |
calendars:read | Get calendar |
| PUT | /calendars/{id} |
calendars:write | Update calendar |
| DELETE | /calendars/{id} |
calendars:write | Delete calendar |
| POST | /calendars/{id}/share |
calendars:write | Share calendar |
| GET | /calendars/{id}/members |
calendars:read | List members |
| DELETE | /calendars/{id}/members/{userID} |
calendars:write | Remove member |
Events
| Method | Endpoint | Scope | Description |
|---|---|---|---|
| GET | /events |
events:read | List events in time range |
| POST | /events |
events:write | Create event |
| GET | /events/{id} |
events:read | Get event |
| PUT | /events/{id} |
events:write | Update event |
| DELETE | /events/{id} |
events:write | Delete event |
| POST | /events/{id}/reminders |
events:write | Add reminders |
| DELETE | /events/{id}/reminders/{reminderID} |
events:write | Remove reminder |
| POST | /events/{id}/attendees |
events:write | Add attendees |
| PUT | /events/{id}/attendees/{attendeeID} |
events:write | Update RSVP |
| DELETE | /events/{id}/attendees/{attendeeID} |
events:write | Remove attendee |
Contacts
| Method | Endpoint | Scope | Description |
|---|---|---|---|
| GET | /contacts |
contacts:read | List contacts |
| POST | /contacts |
contacts:write | Create contact |
| GET | /contacts/{id} |
contacts:read | Get contact |
| PUT | /contacts/{id} |
contacts:write | Update contact |
| DELETE | /contacts/{id} |
contacts:write | Delete contact |
Availability
| Method | Endpoint | Scope | Description |
|---|---|---|---|
| GET | /availability |
availability:read | Get busy blocks for a calendar |
Booking
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| POST | /calendars/{id}/booking-link |
booking:write | Create booking link |
| GET | /booking/{token}/availability |
None | Get available slots |
| POST | /booking/{token}/reserve |
None | Reserve a slot |
ICS
| Method | Endpoint | Scope | Description |
|---|---|---|---|
| GET | /calendars/{id}/export.ics |
calendars:read | Export as ICS |
| POST | /calendars/import |
calendars:write | Import ICS file |
| POST | /calendars/import-url |
calendars:write | Import from iCal URL |
| GET | /cal/{token}/feed.ics |
None (public) | Public iCal feed |
Project Structure
calendarapi/
cmd/server/main.go Entry point
internal/
api/
routes.go Route definitions
handlers/ HTTP handlers
openapi/ OpenAPI spec files + Swagger UI
auth/ JWT manager
config/ Environment config
middleware/ Auth, rate limiting, scope enforcement
models/ Domain models
repository/ sqlc-generated database queries
scheduler/ Asynq background job scheduler
service/ Business logic layer
utils/ Shared utilities
migrations/ SQL migration files
sqlc/ sqlc configuration
about/ Design specifications
overview.md Architecture and data model
logic.md Business logic rules
details.md Endpoint contracts
SKILL.md AI agent integration guide
llms.txt LLM-optimized API reference
.env.example Environment template
go.mod
go.sum
Architecture
Client (Web / Mobile / Agent)
|
HTTP REST API (Chi router)
|
Middleware (Auth, Rate Limiting, Scope Enforcement)
|
Handler Layer (Parse request, validate, return JSON)
|
Service Layer (Business logic, permissions, transactions)
|
Repository Layer (sqlc queries)
|
PostgreSQL
Design Principles
- Stateless - JWT or API key on every request, no server-side sessions
- UTC everywhere - All timestamps stored in UTC, original timezone preserved
- Soft deletes - No data is permanently destroyed
- Strict ownership - Ownership derived from auth context, never from client input
- Cursor pagination - Efficient, consistent pagination on all list endpoints
- Scoped API keys - Fine-grained permission control for agent access
- Race-safe booking - Database transactions with row locking prevent double-booking
License
MIT
Description
Languages
Go
57.4%
TypeScript
42.3%
CSS
0.2%
HTML
0.1%