first commit

Made-with: Cursor
This commit is contained in:
Michilis
2026-02-28 02:17:55 +00:00
commit 41f6ae916f
92 changed files with 12332 additions and 0 deletions

View File

@@ -0,0 +1,101 @@
{
"paths": {
"/api-keys": {
"post": {
"tags": ["API Keys"],
"summary": "Create a new API key",
"description": "Creates a new API key with specified scopes for agent/programmatic access. The raw token is returned only once in the response.",
"operationId": "createApiKey",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["name", "scopes"],
"properties": {
"name": { "type": "string", "example": "My agent key" },
"scopes": {
"type": "object",
"description": "Permission scopes for the API key",
"properties": {
"calendars": { "type": "array", "items": { "type": "string", "enum": ["read", "write"] } },
"events": { "type": "array", "items": { "type": "string", "enum": ["read", "write"] } },
"contacts": { "type": "array", "items": { "type": "string", "enum": ["read", "write"] } },
"availability": { "type": "array", "items": { "type": "string", "enum": ["read"] } },
"booking": { "type": "array", "items": { "type": "string", "enum": ["write"] } }
},
"example": {
"calendars": ["read", "write"],
"events": ["read", "write"],
"contacts": ["read"],
"availability": ["read"]
}
}
}
}
}
}
},
"responses": {
"200": {
"description": "API key created (token shown only once)",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/APIKeyResponse" }
}
}
},
"400": { "description": "Validation error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"401": { "description": "Not authenticated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
}
},
"get": {
"tags": ["API Keys"],
"summary": "List API keys",
"description": "Returns all API keys for the authenticated user. Tokens are never returned in list responses.",
"operationId": "listApiKeys",
"responses": {
"200": {
"description": "List of API keys",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["items", "page"],
"properties": {
"items": { "type": "array", "items": { "$ref": "#/components/schemas/APIKeyResponse" } },
"page": { "$ref": "#/components/schemas/PageInfo" }
}
}
}
}
},
"401": { "description": "Not authenticated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
}
}
},
"/api-keys/{id}": {
"delete": {
"tags": ["API Keys"],
"summary": "Revoke an API key",
"description": "Revokes the specified API key, preventing further use.",
"operationId": "revokeApiKey",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": { "type": "string", "format": "uuid" },
"description": "API key ID"
}
],
"responses": {
"200": { "description": "API key revoked", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/OkResponse" } } } },
"401": { "description": "Not authenticated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"404": { "description": "API key not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
}
}
}
}
}

View File

@@ -0,0 +1,183 @@
{
"paths": {
"/auth/register": {
"post": {
"tags": ["Auth"],
"summary": "Register a new user",
"description": "Creates a new user account with email and password. A default calendar is automatically created. Returns the user profile along with access and refresh tokens.",
"operationId": "registerUser",
"security": [],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["email", "password"],
"properties": {
"email": { "type": "string", "format": "email", "example": "user@example.com" },
"password": { "type": "string", "minLength": 10, "example": "securepassword123" },
"timezone": { "type": "string", "example": "America/Asuncion", "description": "IANA timezone name, defaults to UTC" }
}
}
}
}
},
"responses": {
"200": {
"description": "User registered successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["user", "access_token", "refresh_token"],
"properties": {
"user": { "$ref": "#/components/schemas/User" },
"access_token": { "type": "string" },
"refresh_token": { "type": "string" }
}
}
}
}
},
"400": { "description": "Validation error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"409": { "description": "Email already exists", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
}
}
},
"/auth/login": {
"post": {
"tags": ["Auth"],
"summary": "Login with credentials",
"description": "Authenticates a user with email and password. Returns the user profile along with access and refresh tokens.",
"operationId": "loginUser",
"security": [],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["email", "password"],
"properties": {
"email": { "type": "string", "format": "email", "example": "user@example.com" },
"password": { "type": "string", "example": "securepassword123" }
}
}
}
}
},
"responses": {
"200": {
"description": "Login successful",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["user", "access_token", "refresh_token"],
"properties": {
"user": { "$ref": "#/components/schemas/User" },
"access_token": { "type": "string" },
"refresh_token": { "type": "string" }
}
}
}
}
},
"401": { "description": "Invalid credentials", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
}
}
},
"/auth/refresh": {
"post": {
"tags": ["Auth"],
"summary": "Refresh access token",
"description": "Exchanges a valid refresh token for a new access/refresh token pair.",
"operationId": "refreshToken",
"security": [],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["refresh_token"],
"properties": {
"refresh_token": { "type": "string" }
}
}
}
}
},
"responses": {
"200": {
"description": "Tokens refreshed",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["access_token", "refresh_token"],
"properties": {
"access_token": { "type": "string" },
"refresh_token": { "type": "string" }
}
}
}
}
},
"401": { "description": "Invalid or expired refresh token", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
}
}
},
"/auth/logout": {
"post": {
"tags": ["Auth"],
"summary": "Logout and revoke refresh token",
"description": "Revokes the provided refresh token, ending the session.",
"operationId": "logoutUser",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["refresh_token"],
"properties": {
"refresh_token": { "type": "string" }
}
}
}
}
},
"responses": {
"200": { "description": "Logged out", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/OkResponse" } } } }
}
}
},
"/auth/me": {
"get": {
"tags": ["Auth"],
"summary": "Get current authenticated user",
"description": "Returns the profile of the currently authenticated user.",
"operationId": "getCurrentUser",
"responses": {
"200": {
"description": "Current user",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["user"],
"properties": {
"user": { "$ref": "#/components/schemas/User" }
}
}
}
}
},
"401": { "description": "Not authenticated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
}
}
}
}
}

View File

@@ -0,0 +1,42 @@
{
"paths": {
"/availability": {
"get": {
"tags": ["Availability"],
"summary": "Get calendar availability",
"description": "Returns busy time blocks for a calendar within a given range. Includes expanded recurring event occurrences. User must have at least viewer role on the calendar. Requires `availability:read` scope.",
"operationId": "getAvailability",
"parameters": [
{ "name": "calendar_id", "in": "query", "required": true, "schema": { "type": "string", "format": "uuid" }, "description": "Calendar to query" },
{ "name": "start", "in": "query", "required": true, "schema": { "type": "string", "format": "date-time" }, "description": "Range start (RFC3339)" },
{ "name": "end", "in": "query", "required": true, "schema": { "type": "string", "format": "date-time" }, "description": "Range end (RFC3339)" }
],
"responses": {
"200": {
"description": "Availability with busy blocks",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["calendar_id", "range_start", "range_end", "busy"],
"properties": {
"calendar_id": { "type": "string", "format": "uuid" },
"range_start": { "type": "string", "format": "date-time" },
"range_end": { "type": "string", "format": "date-time" },
"busy": {
"type": "array",
"items": { "$ref": "#/components/schemas/BusyBlock" }
}
}
}
}
}
},
"400": { "description": "Validation error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"401": { "description": "Not authenticated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"403": { "description": "Insufficient scope or permission", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
}
}
}
}
}

View File

@@ -0,0 +1,41 @@
{
"openapi": "3.1.0",
"info": {
"title": "Calendar & Contacts API",
"description": "Production-grade Calendar and Contacts REST API supporting human users, AI agents, and programmatic automation. Features JWT and API key authentication, calendar sharing, recurring events, booking links, ICS import/export, and background reminder processing.",
"version": "1.0.0",
"contact": {
"name": "API Support"
},
"license": {
"name": "MIT"
}
},
"servers": [
{
"url": "http://localhost:8080",
"description": "Local development"
},
{
"url": "https://api.example.com",
"description": "Production"
}
],
"tags": [
{ "name": "Auth", "description": "Authentication and session management" },
{ "name": "Users", "description": "User profile management" },
{ "name": "API Keys", "description": "API key management for agents" },
{ "name": "Calendars", "description": "Calendar CRUD and sharing" },
{ "name": "Events", "description": "Event CRUD with recurrence support" },
{ "name": "Reminders", "description": "Event reminder management" },
{ "name": "Attendees", "description": "Event attendee management" },
{ "name": "Contacts", "description": "Contact management" },
{ "name": "Availability", "description": "Calendar availability queries" },
{ "name": "Booking", "description": "Public booking links and reservations" },
{ "name": "ICS", "description": "ICS calendar import and export" }
],
"security": [
{ "BearerAuth": [] },
{ "ApiKeyAuth": [] }
]
}

View File

@@ -0,0 +1,191 @@
{
"paths": {
"/calendars/{id}/booking-link": {
"post": {
"tags": ["Booking"],
"summary": "Create a booking link",
"description": "Creates a public booking link for a calendar with configurable duration, buffer time, working hours, and timezone. Only the calendar owner can create booking links. Requires `booking:write` scope.",
"operationId": "createBookingLink",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": { "type": "string", "format": "uuid" },
"description": "Calendar ID"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["duration_minutes", "timezone", "working_hours"],
"properties": {
"duration_minutes": { "type": "integer", "minimum": 5, "example": 30 },
"buffer_minutes": { "type": "integer", "minimum": 0, "default": 0, "example": 0 },
"timezone": { "type": "string", "example": "America/Asuncion" },
"working_hours": {
"type": "object",
"description": "Working hour windows per day of week",
"properties": {
"mon": { "type": "array", "items": { "$ref": "#/components/schemas/WorkingHourSlot" } },
"tue": { "type": "array", "items": { "$ref": "#/components/schemas/WorkingHourSlot" } },
"wed": { "type": "array", "items": { "$ref": "#/components/schemas/WorkingHourSlot" } },
"thu": { "type": "array", "items": { "$ref": "#/components/schemas/WorkingHourSlot" } },
"fri": { "type": "array", "items": { "$ref": "#/components/schemas/WorkingHourSlot" } },
"sat": { "type": "array", "items": { "$ref": "#/components/schemas/WorkingHourSlot" } },
"sun": { "type": "array", "items": { "$ref": "#/components/schemas/WorkingHourSlot" } }
},
"example": {
"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": { "type": "boolean", "default": true }
}
}
}
}
},
"responses": {
"200": {
"description": "Booking link created",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["token", "settings"],
"properties": {
"token": { "type": "string" },
"public_url": { "type": "string", "format": "uri", "example": "https://app.example.com/booking/abc123" },
"settings": {
"type": "object",
"properties": {
"duration_minutes": { "type": "integer" },
"buffer_minutes": { "type": "integer" },
"timezone": { "type": "string" },
"working_hours": { "type": "object" },
"active": { "type": "boolean" }
}
}
}
}
}
}
},
"400": { "description": "Validation error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"401": { "description": "Not authenticated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"403": { "description": "Only owner can create booking links", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"404": { "description": "Calendar not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
}
}
},
"/booking/{token}/availability": {
"get": {
"tags": ["Booking"],
"summary": "Get public booking availability",
"description": "Returns available time slots for a public booking link within a date range. No authentication required. Computes available slots by subtracting busy blocks and applying buffer time to the working hour windows.",
"operationId": "getBookingAvailability",
"security": [],
"parameters": [
{
"name": "token",
"in": "path",
"required": true,
"schema": { "type": "string" },
"description": "Booking link token"
},
{ "name": "start", "in": "query", "required": true, "schema": { "type": "string", "format": "date-time" }, "description": "Range start (RFC3339)" },
{ "name": "end", "in": "query", "required": true, "schema": { "type": "string", "format": "date-time" }, "description": "Range end (RFC3339)" }
],
"responses": {
"200": {
"description": "Available booking slots",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["token", "timezone", "duration_minutes", "slots"],
"properties": {
"token": { "type": "string" },
"timezone": { "type": "string" },
"duration_minutes": { "type": "integer" },
"slots": {
"type": "array",
"items": { "$ref": "#/components/schemas/TimeSlot" }
}
}
}
}
}
},
"400": { "description": "Validation error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"404": { "description": "Booking link not found or inactive", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
}
}
},
"/booking/{token}/reserve": {
"post": {
"tags": ["Booking"],
"summary": "Reserve a booking slot",
"description": "Reserves a time slot on a public booking link. Creates an event on the calendar. Uses a database transaction with row locking to prevent double-booking. No authentication required.",
"operationId": "reserveBookingSlot",
"security": [],
"parameters": [
{
"name": "token",
"in": "path",
"required": true,
"schema": { "type": "string" },
"description": "Booking link token"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["name", "email", "slot_start", "slot_end"],
"properties": {
"name": { "type": "string", "example": "Visitor Name" },
"email": { "type": "string", "format": "email", "example": "visitor@example.com" },
"slot_start": { "type": "string", "format": "date-time" },
"slot_end": { "type": "string", "format": "date-time" },
"notes": { "type": "string", "example": "Looking forward to the meeting" }
}
}
}
}
},
"responses": {
"200": {
"description": "Booking confirmed",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["ok", "event"],
"properties": {
"ok": { "type": "boolean", "example": true },
"event": { "$ref": "#/components/schemas/Event" }
}
}
}
}
},
"400": { "description": "Validation error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"404": { "description": "Booking link not found or inactive", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"409": { "description": "Slot no longer available (conflict)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
}
}
}
}
}

View File

@@ -0,0 +1,291 @@
{
"paths": {
"/calendars": {
"get": {
"tags": ["Calendars"],
"summary": "List calendars",
"description": "Returns all calendars the user owns or has been shared with. Each calendar includes the user's role (owner, editor, or viewer). Requires `calendars:read` scope.",
"operationId": "listCalendars",
"responses": {
"200": {
"description": "List of calendars",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["items", "page"],
"properties": {
"items": { "type": "array", "items": { "$ref": "#/components/schemas/Calendar" } },
"page": { "$ref": "#/components/schemas/PageInfo" }
}
}
}
}
},
"401": { "description": "Not authenticated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"403": { "description": "Insufficient scope", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
}
},
"post": {
"tags": ["Calendars"],
"summary": "Create a calendar",
"description": "Creates a new calendar owned by the authenticated user. Requires `calendars:write` scope.",
"operationId": "createCalendar",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["name"],
"properties": {
"name": { "type": "string", "minLength": 1, "maxLength": 80, "example": "Work" },
"color": { "type": "string", "pattern": "^#[0-9A-Fa-f]{6}$", "example": "#22C55E" }
}
}
}
}
},
"responses": {
"200": {
"description": "Calendar created",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["calendar"],
"properties": {
"calendar": { "$ref": "#/components/schemas/Calendar" }
}
}
}
}
},
"400": { "description": "Validation error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"401": { "description": "Not authenticated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"403": { "description": "Insufficient scope", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
}
}
},
"/calendars/{id}": {
"get": {
"tags": ["Calendars"],
"summary": "Get a calendar",
"description": "Returns a single calendar by ID. User must be owner or member. Requires `calendars:read` scope.",
"operationId": "getCalendar",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": { "type": "string", "format": "uuid" },
"description": "Calendar ID"
}
],
"responses": {
"200": {
"description": "Calendar details",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["calendar"],
"properties": {
"calendar": { "$ref": "#/components/schemas/Calendar" }
}
}
}
}
},
"401": { "description": "Not authenticated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"403": { "description": "Insufficient scope or permission", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"404": { "description": "Calendar not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
}
},
"put": {
"tags": ["Calendars"],
"summary": "Update a calendar",
"description": "Updates a calendar's name, color, or public status. Only the owner can change `is_public`. Requires `calendars:write` scope.",
"operationId": "updateCalendar",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": { "type": "string", "format": "uuid" },
"description": "Calendar ID"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"name": { "type": "string", "minLength": 1, "maxLength": 80, "example": "Work Calendar" },
"color": { "type": "string", "pattern": "^#[0-9A-Fa-f]{6}$", "example": "#22C55E" },
"is_public": { "type": "boolean" }
}
}
}
}
},
"responses": {
"200": {
"description": "Calendar updated",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["calendar"],
"properties": {
"calendar": { "$ref": "#/components/schemas/Calendar" }
}
}
}
}
},
"400": { "description": "Validation error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"401": { "description": "Not authenticated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"403": { "description": "Insufficient scope or permission", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"404": { "description": "Calendar not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
}
},
"delete": {
"tags": ["Calendars"],
"summary": "Delete a calendar",
"description": "Soft-deletes a calendar and all its events. Only the owner can delete. Requires `calendars:write` scope.",
"operationId": "deleteCalendar",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": { "type": "string", "format": "uuid" },
"description": "Calendar ID"
}
],
"responses": {
"200": { "description": "Calendar deleted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/OkResponse" } } } },
"401": { "description": "Not authenticated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"403": { "description": "Only owner can delete", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"404": { "description": "Calendar not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
}
}
},
"/calendars/{id}/share": {
"post": {
"tags": ["Calendars"],
"summary": "Share a calendar",
"description": "Shares a calendar with another user by email, granting them a role (editor or viewer). Only the owner can share. Cannot share with self. Requires `calendars:write` scope.",
"operationId": "shareCalendar",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": { "type": "string", "format": "uuid" },
"description": "Calendar ID"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["target", "role"],
"properties": {
"target": {
"type": "object",
"required": ["email"],
"properties": {
"email": { "type": "string", "format": "email", "example": "other@example.com" }
}
},
"role": { "type": "string", "enum": ["editor", "viewer"], "example": "editor" }
}
}
}
}
},
"responses": {
"200": { "description": "Calendar shared", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/OkResponse" } } } },
"400": { "description": "Validation error (e.g. sharing with self)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"401": { "description": "Not authenticated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"403": { "description": "Only owner can share", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"404": { "description": "Calendar or target user not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
}
}
},
"/calendars/{id}/members": {
"get": {
"tags": ["Calendars"],
"summary": "List calendar members",
"description": "Returns all members of a calendar with their roles. Requires `calendars:read` scope.",
"operationId": "listCalendarMembers",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": { "type": "string", "format": "uuid" },
"description": "Calendar ID"
}
],
"responses": {
"200": {
"description": "List of members",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["items", "page"],
"properties": {
"items": { "type": "array", "items": { "$ref": "#/components/schemas/CalendarMember" } },
"page": { "$ref": "#/components/schemas/PageInfo" }
}
}
}
}
},
"401": { "description": "Not authenticated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"403": { "description": "Insufficient scope or permission", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"404": { "description": "Calendar not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
}
}
},
"/calendars/{id}/members/{userID}": {
"delete": {
"tags": ["Calendars"],
"summary": "Remove a calendar member",
"description": "Removes a member from a shared calendar. Only the owner can remove members. The owner cannot be removed. Requires `calendars:write` scope.",
"operationId": "removeCalendarMember",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": { "type": "string", "format": "uuid" },
"description": "Calendar ID"
},
{
"name": "userID",
"in": "path",
"required": true,
"schema": { "type": "string", "format": "uuid" },
"description": "User ID of the member to remove"
}
],
"responses": {
"200": { "description": "Member removed", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/OkResponse" } } } },
"400": { "description": "Cannot remove owner", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"401": { "description": "Not authenticated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"403": { "description": "Only owner can remove members", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"404": { "description": "Calendar or member not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
}
}
}
}
}

View File

@@ -0,0 +1,189 @@
{
"paths": {
"/contacts": {
"get": {
"tags": ["Contacts"],
"summary": "List contacts",
"description": "Returns the authenticated user's contacts. Supports search (case-insensitive match on first_name, last_name, email, company) and cursor-based pagination. Requires `contacts:read` scope.",
"operationId": "listContacts",
"parameters": [
{ "name": "search", "in": "query", "schema": { "type": "string" }, "description": "Search term for name, email, or company" },
{ "name": "limit", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 200, "default": 50 }, "description": "Page size" },
{ "name": "cursor", "in": "query", "schema": { "type": "string" }, "description": "Pagination cursor" }
],
"responses": {
"200": {
"description": "List of contacts",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["items", "page"],
"properties": {
"items": { "type": "array", "items": { "$ref": "#/components/schemas/Contact" } },
"page": { "$ref": "#/components/schemas/PageInfo" }
}
}
}
}
},
"401": { "description": "Not authenticated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"403": { "description": "Insufficient scope", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
}
},
"post": {
"tags": ["Contacts"],
"summary": "Create a contact",
"description": "Creates a new contact for the authenticated user. At least one identifying field (first_name, last_name, email, or phone) must be provided. Requires `contacts:write` scope.",
"operationId": "createContact",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"first_name": { "type": "string", "example": "Jane" },
"last_name": { "type": "string", "example": "Doe" },
"email": { "type": "string", "format": "email", "example": "jane@example.com" },
"phone": { "type": "string", "example": "+595981000000" },
"company": { "type": "string", "example": "Example SA" },
"notes": { "type": "string", "example": "Met at event" }
}
}
}
}
},
"responses": {
"200": {
"description": "Contact created",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["contact"],
"properties": {
"contact": { "$ref": "#/components/schemas/Contact" }
}
}
}
}
},
"400": { "description": "Validation error (e.g. no identifying field)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"401": { "description": "Not authenticated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"403": { "description": "Insufficient scope", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
}
}
},
"/contacts/{id}": {
"get": {
"tags": ["Contacts"],
"summary": "Get a contact",
"description": "Returns a single contact by ID. Only the owner can access their contacts. Requires `contacts:read` scope.",
"operationId": "getContact",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": { "type": "string", "format": "uuid" },
"description": "Contact ID"
}
],
"responses": {
"200": {
"description": "Contact details",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["contact"],
"properties": {
"contact": { "$ref": "#/components/schemas/Contact" }
}
}
}
}
},
"401": { "description": "Not authenticated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"403": { "description": "Insufficient scope", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"404": { "description": "Contact not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
}
},
"put": {
"tags": ["Contacts"],
"summary": "Update a contact",
"description": "Updates a contact's fields. Only the owner can update their contacts. Requires `contacts:write` scope.",
"operationId": "updateContact",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": { "type": "string", "format": "uuid" },
"description": "Contact ID"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"first_name": { "type": "string" },
"last_name": { "type": "string" },
"email": { "type": "string", "format": "email" },
"phone": { "type": "string" },
"company": { "type": "string" },
"notes": { "type": "string" }
}
}
}
}
},
"responses": {
"200": {
"description": "Contact updated",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["contact"],
"properties": {
"contact": { "$ref": "#/components/schemas/Contact" }
}
}
}
}
},
"400": { "description": "Validation error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"401": { "description": "Not authenticated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"403": { "description": "Insufficient scope", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"404": { "description": "Contact not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
}
},
"delete": {
"tags": ["Contacts"],
"summary": "Delete a contact",
"description": "Soft-deletes a contact. Only the owner can delete their contacts. Requires `contacts:write` scope.",
"operationId": "deleteContact",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": { "type": "string", "format": "uuid" },
"description": "Contact ID"
}
],
"responses": {
"200": { "description": "Contact deleted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/OkResponse" } } } },
"401": { "description": "Not authenticated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"403": { "description": "Insufficient scope", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"404": { "description": "Contact not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
}
}
}
}
}

View File

@@ -0,0 +1,438 @@
{
"paths": {
"/events": {
"get": {
"tags": ["Events"],
"summary": "List events",
"description": "Returns events within a time range across all accessible calendars. Recurring events are expanded into individual occurrences within the requested range. Supports filtering by calendar, search text, and tags. Uses cursor-based pagination. Requires `events:read` scope.",
"operationId": "listEvents",
"parameters": [
{ "name": "start", "in": "query", "required": true, "schema": { "type": "string", "format": "date-time" }, "description": "Range start (RFC3339)" },
{ "name": "end", "in": "query", "required": true, "schema": { "type": "string", "format": "date-time" }, "description": "Range end (RFC3339)" },
{ "name": "calendar_id", "in": "query", "schema": { "type": "string", "format": "uuid" }, "description": "Filter by calendar" },
{ "name": "search", "in": "query", "schema": { "type": "string" }, "description": "Full-text search on title/description" },
{ "name": "tag", "in": "query", "schema": { "type": "string" }, "description": "Filter by tag" },
{ "name": "limit", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 200, "default": 50 }, "description": "Page size" },
{ "name": "cursor", "in": "query", "schema": { "type": "string" }, "description": "Pagination cursor from previous response" }
],
"responses": {
"200": {
"description": "List of events (including expanded recurrence occurrences)",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["items", "page"],
"properties": {
"items": { "type": "array", "items": { "$ref": "#/components/schemas/Event" } },
"page": { "$ref": "#/components/schemas/PageInfo" }
}
}
}
}
},
"400": { "description": "Validation error (e.g. missing start/end, range too large)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"401": { "description": "Not authenticated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"403": { "description": "Insufficient scope", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
}
},
"post": {
"tags": ["Events"],
"summary": "Create an event",
"description": "Creates a new event on the specified calendar. Times are converted to UTC for storage. Supports recurrence rules (RFC5545 RRULE), reminders, and tags. User must have editor or owner role on the calendar. Requires `events:write` scope.",
"operationId": "createEvent",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["calendar_id", "title", "start_time", "end_time", "timezone"],
"properties": {
"calendar_id": { "type": "string", "format": "uuid" },
"title": { "type": "string", "minLength": 1, "maxLength": 140, "example": "Meeting" },
"description": { "type": "string", "example": "Project sync" },
"location": { "type": "string", "example": "Zoom" },
"start_time": { "type": "string", "format": "date-time", "example": "2026-03-01T14:00:00-03:00" },
"end_time": { "type": "string", "format": "date-time", "example": "2026-03-01T15:00:00-03:00" },
"timezone": { "type": "string", "example": "America/Asuncion" },
"all_day": { "type": "boolean", "default": false },
"recurrence_rule": { "type": "string", "nullable": true, "example": "FREQ=WEEKLY;BYDAY=MO,WE,FR" },
"reminders": { "type": "array", "items": { "type": "integer", "minimum": 0, "maximum": 10080 }, "description": "Minutes before event to remind", "example": [10, 60] },
"tags": { "type": "array", "items": { "type": "string" }, "example": ["work", "sync"] }
}
}
}
}
},
"responses": {
"200": {
"description": "Event created",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["event"],
"properties": {
"event": { "$ref": "#/components/schemas/Event" }
}
}
}
}
},
"400": { "description": "Validation error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"401": { "description": "Not authenticated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"403": { "description": "Insufficient scope or calendar permission", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
}
}
},
"/events/{id}": {
"get": {
"tags": ["Events"],
"summary": "Get an event",
"description": "Returns a single event by ID with all related data (reminders, attendees, tags, attachments). Requires `events:read` scope.",
"operationId": "getEvent",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": { "type": "string", "format": "uuid" },
"description": "Event ID"
}
],
"responses": {
"200": {
"description": "Event details",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["event"],
"properties": {
"event": { "$ref": "#/components/schemas/Event" }
}
}
}
}
},
"401": { "description": "Not authenticated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"403": { "description": "Insufficient scope or permission", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"404": { "description": "Event not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
}
},
"put": {
"tags": ["Events"],
"summary": "Update an event",
"description": "Updates an existing event. Times are re-validated and converted to UTC. If recurrence rule changes, it is re-validated. Reminders are rescheduled. Requires `events:write` scope and editor/owner role.",
"operationId": "updateEvent",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": { "type": "string", "format": "uuid" },
"description": "Event ID"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"title": { "type": "string", "minLength": 1, "maxLength": 140 },
"description": { "type": "string", "nullable": true },
"location": { "type": "string", "nullable": true },
"start_time": { "type": "string", "format": "date-time" },
"end_time": { "type": "string", "format": "date-time" },
"timezone": { "type": "string" },
"all_day": { "type": "boolean" },
"recurrence_rule": { "type": "string", "nullable": true },
"tags": { "type": "array", "items": { "type": "string" } }
}
}
}
}
},
"responses": {
"200": {
"description": "Event updated",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["event"],
"properties": {
"event": { "$ref": "#/components/schemas/Event" }
}
}
}
}
},
"400": { "description": "Validation error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"401": { "description": "Not authenticated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"403": { "description": "Insufficient scope or permission", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"404": { "description": "Event not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
}
},
"delete": {
"tags": ["Events"],
"summary": "Delete an event",
"description": "Soft-deletes an event. Requires `events:write` scope and editor/owner role on the calendar.",
"operationId": "deleteEvent",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": { "type": "string", "format": "uuid" },
"description": "Event ID"
}
],
"responses": {
"200": { "description": "Event deleted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/OkResponse" } } } },
"401": { "description": "Not authenticated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"403": { "description": "Insufficient scope or permission", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"404": { "description": "Event not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
}
}
},
"/events/{id}/reminders": {
"post": {
"tags": ["Reminders"],
"summary": "Add reminders to an event",
"description": "Adds one or more reminders to an event, specified as minutes before the event start. Background jobs are scheduled for each reminder. Requires `events:write` scope.",
"operationId": "addReminders",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": { "type": "string", "format": "uuid" },
"description": "Event ID"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["minutes_before"],
"properties": {
"minutes_before": {
"type": "array",
"items": { "type": "integer", "minimum": 0, "maximum": 10080 },
"example": [5, 15, 60]
}
}
}
}
}
},
"responses": {
"200": {
"description": "Reminders added, returns updated event",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["event"],
"properties": {
"event": { "$ref": "#/components/schemas/Event" }
}
}
}
}
},
"400": { "description": "Validation error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"401": { "description": "Not authenticated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"403": { "description": "Insufficient scope or permission", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"404": { "description": "Event not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
}
}
},
"/events/{id}/reminders/{reminderID}": {
"delete": {
"tags": ["Reminders"],
"summary": "Delete a reminder",
"description": "Removes a specific reminder from an event. Requires `events:write` scope.",
"operationId": "deleteReminder",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": { "type": "string", "format": "uuid" },
"description": "Event ID"
},
{
"name": "reminderID",
"in": "path",
"required": true,
"schema": { "type": "string", "format": "uuid" },
"description": "Reminder ID"
}
],
"responses": {
"200": { "description": "Reminder deleted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/OkResponse" } } } },
"401": { "description": "Not authenticated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"403": { "description": "Insufficient scope or permission", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"404": { "description": "Event or reminder not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
}
}
},
"/events/{id}/attendees": {
"post": {
"tags": ["Attendees"],
"summary": "Add attendees to an event",
"description": "Adds one or more attendees to an event, identified by email or user ID. Initial status is `pending`. Requires `events:write` scope.",
"operationId": "addAttendees",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": { "type": "string", "format": "uuid" },
"description": "Event ID"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["attendees"],
"properties": {
"attendees": {
"type": "array",
"items": {
"type": "object",
"properties": {
"email": { "type": "string", "format": "email" },
"user_id": { "type": "string", "format": "uuid" }
}
},
"example": [
{ "email": "guest@example.com" },
{ "user_id": "550e8400-e29b-41d4-a716-446655440000" }
]
}
}
}
}
}
},
"responses": {
"200": {
"description": "Attendees added, returns updated event",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["event"],
"properties": {
"event": { "$ref": "#/components/schemas/Event" }
}
}
}
}
},
"400": { "description": "Validation error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"401": { "description": "Not authenticated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"403": { "description": "Insufficient scope or permission", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"404": { "description": "Event not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
}
}
},
"/events/{id}/attendees/{attendeeID}": {
"put": {
"tags": ["Attendees"],
"summary": "Update attendee status",
"description": "Updates an attendee's RSVP status. The event organizer can update any attendee; attendees can update their own status. Requires `events:write` scope.",
"operationId": "updateAttendeeStatus",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": { "type": "string", "format": "uuid" },
"description": "Event ID"
},
{
"name": "attendeeID",
"in": "path",
"required": true,
"schema": { "type": "string", "format": "uuid" },
"description": "Attendee ID"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["status"],
"properties": {
"status": { "type": "string", "enum": ["accepted", "declined", "tentative"] }
}
}
}
}
},
"responses": {
"200": {
"description": "Attendee updated, returns updated event",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["event"],
"properties": {
"event": { "$ref": "#/components/schemas/Event" }
}
}
}
}
},
"400": { "description": "Validation error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"401": { "description": "Not authenticated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"403": { "description": "Insufficient scope or permission", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"404": { "description": "Event or attendee not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
}
},
"delete": {
"tags": ["Attendees"],
"summary": "Remove an attendee",
"description": "Removes an attendee from an event. Requires `events:write` scope.",
"operationId": "deleteAttendee",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": { "type": "string", "format": "uuid" },
"description": "Event ID"
},
{
"name": "attendeeID",
"in": "path",
"required": true,
"schema": { "type": "string", "format": "uuid" },
"description": "Attendee ID"
}
],
"responses": {
"200": { "description": "Attendee removed", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/OkResponse" } } } },
"401": { "description": "Not authenticated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"403": { "description": "Insufficient scope or permission", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"404": { "description": "Event or attendee not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
}
}
}
}
}

View File

@@ -0,0 +1,82 @@
{
"paths": {
"/calendars/{id}/export.ics": {
"get": {
"tags": ["ICS"],
"summary": "Export calendar as ICS",
"description": "Exports all events from a calendar in ICS (iCalendar) format. Requires `calendars:read` scope.",
"operationId": "exportCalendarICS",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": { "type": "string", "format": "uuid" },
"description": "Calendar ID"
}
],
"responses": {
"200": {
"description": "ICS calendar file",
"content": {
"text/calendar": {
"schema": { "type": "string" }
}
}
},
"401": { "description": "Not authenticated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"403": { "description": "Insufficient scope or permission", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"404": { "description": "Calendar not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
}
}
},
"/calendars/import": {
"post": {
"tags": ["ICS"],
"summary": "Import an ICS file",
"description": "Imports events from an ICS file into a specified calendar. The file is sent as multipart form data. Requires `calendars:write` scope.",
"operationId": "importCalendarICS",
"requestBody": {
"required": true,
"content": {
"multipart/form-data": {
"schema": {
"type": "object",
"required": ["calendar_id", "file"],
"properties": {
"calendar_id": { "type": "string", "format": "uuid", "description": "Target calendar ID" },
"file": { "type": "string", "format": "binary", "description": "ICS file to import" }
}
}
}
}
},
"responses": {
"200": {
"description": "Import successful",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["ok", "imported"],
"properties": {
"ok": { "type": "boolean", "example": true },
"imported": {
"type": "object",
"properties": {
"events": { "type": "integer", "example": 12 }
}
}
}
}
}
}
},
"400": { "description": "Validation error or invalid ICS", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"401": { "description": "Not authenticated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"403": { "description": "Insufficient scope or permission", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
}
}
}
}
}

View File

@@ -0,0 +1,181 @@
{
"components": {
"securitySchemes": {
"BearerAuth": {
"type": "http",
"scheme": "bearer",
"bearerFormat": "JWT",
"description": "JWT access token obtained from /auth/login or /auth/register"
},
"ApiKeyAuth": {
"type": "apiKey",
"in": "header",
"name": "X-API-Key",
"description": "API key token for agent/programmatic access with scoped permissions"
}
},
"schemas": {
"Error": {
"type": "object",
"required": ["error", "code"],
"properties": {
"error": { "type": "string", "description": "Human-readable error message" },
"code": { "type": "string", "description": "Machine-readable error code", "enum": ["VALIDATION_ERROR", "AUTH_REQUIRED", "AUTH_INVALID", "FORBIDDEN", "NOT_FOUND", "CONFLICT", "RATE_LIMITED", "INTERNAL"] },
"details": { "description": "Additional error context" }
}
},
"OkResponse": {
"type": "object",
"required": ["ok"],
"properties": {
"ok": { "type": "boolean", "example": true }
}
},
"PageInfo": {
"type": "object",
"required": ["limit", "next_cursor"],
"properties": {
"limit": { "type": "integer", "example": 50 },
"next_cursor": { "type": "string", "nullable": true, "description": "Opaque cursor for next page, null if no more results" }
}
},
"User": {
"type": "object",
"required": ["id", "email", "timezone", "created_at", "updated_at"],
"properties": {
"id": { "type": "string", "format": "uuid" },
"email": { "type": "string", "format": "email" },
"timezone": { "type": "string", "example": "America/New_York", "description": "IANA timezone name" },
"created_at": { "type": "string", "format": "date-time" },
"updated_at": { "type": "string", "format": "date-time" }
}
},
"Calendar": {
"type": "object",
"required": ["id", "name", "color", "is_public", "created_at", "updated_at"],
"properties": {
"id": { "type": "string", "format": "uuid" },
"name": { "type": "string", "minLength": 1, "maxLength": 80 },
"color": { "type": "string", "pattern": "^#[0-9A-Fa-f]{6}$", "example": "#22C55E" },
"is_public": { "type": "boolean" },
"role": { "type": "string", "enum": ["owner", "editor", "viewer"], "description": "Current user's role on this calendar" },
"created_at": { "type": "string", "format": "date-time" },
"updated_at": { "type": "string", "format": "date-time" }
}
},
"Reminder": {
"type": "object",
"required": ["id", "minutes_before"],
"properties": {
"id": { "type": "string", "format": "uuid" },
"minutes_before": { "type": "integer", "minimum": 0, "maximum": 10080 }
}
},
"Attendee": {
"type": "object",
"required": ["id", "status"],
"properties": {
"id": { "type": "string", "format": "uuid" },
"user_id": { "type": "string", "format": "uuid", "nullable": true },
"email": { "type": "string", "format": "email", "nullable": true },
"status": { "type": "string", "enum": ["pending", "accepted", "declined", "tentative"] }
}
},
"Attachment": {
"type": "object",
"required": ["id", "file_url"],
"properties": {
"id": { "type": "string", "format": "uuid" },
"file_url": { "type": "string", "format": "uri" }
}
},
"Event": {
"type": "object",
"required": ["id", "calendar_id", "title", "start_time", "end_time", "timezone", "all_day", "created_by", "updated_by", "created_at", "updated_at"],
"properties": {
"id": { "type": "string", "format": "uuid" },
"calendar_id": { "type": "string", "format": "uuid" },
"title": { "type": "string", "minLength": 1, "maxLength": 140 },
"description": { "type": "string", "nullable": true },
"location": { "type": "string", "nullable": true },
"start_time": { "type": "string", "format": "date-time", "description": "UTC start time in RFC3339" },
"end_time": { "type": "string", "format": "date-time", "description": "UTC end time in RFC3339" },
"timezone": { "type": "string", "example": "America/Asuncion", "description": "Original IANA timezone" },
"all_day": { "type": "boolean" },
"recurrence_rule": { "type": "string", "nullable": true, "description": "RFC5545 RRULE string", "example": "FREQ=WEEKLY;BYDAY=MO,WE,FR" },
"is_occurrence": { "type": "boolean", "description": "True if this is an expanded recurrence occurrence" },
"occurrence_start_time": { "type": "string", "format": "date-time", "nullable": true },
"occurrence_end_time": { "type": "string", "format": "date-time", "nullable": true },
"created_by": { "type": "string", "format": "uuid" },
"updated_by": { "type": "string", "format": "uuid" },
"created_at": { "type": "string", "format": "date-time" },
"updated_at": { "type": "string", "format": "date-time" },
"reminders": { "type": "array", "items": { "$ref": "#/components/schemas/Reminder" } },
"attendees": { "type": "array", "items": { "$ref": "#/components/schemas/Attendee" } },
"tags": { "type": "array", "items": { "type": "string" } },
"attachments": { "type": "array", "items": { "$ref": "#/components/schemas/Attachment" } }
}
},
"Contact": {
"type": "object",
"required": ["id", "created_at", "updated_at"],
"properties": {
"id": { "type": "string", "format": "uuid" },
"first_name": { "type": "string", "nullable": true },
"last_name": { "type": "string", "nullable": true },
"email": { "type": "string", "format": "email", "nullable": true },
"phone": { "type": "string", "nullable": true },
"company": { "type": "string", "nullable": true },
"notes": { "type": "string", "nullable": true },
"created_at": { "type": "string", "format": "date-time" },
"updated_at": { "type": "string", "format": "date-time" }
}
},
"APIKeyResponse": {
"type": "object",
"required": ["id", "name", "created_at"],
"properties": {
"id": { "type": "string", "format": "uuid" },
"name": { "type": "string" },
"created_at": { "type": "string", "format": "date-time" },
"revoked_at": { "type": "string", "format": "date-time", "nullable": true },
"token": { "type": "string", "description": "Raw token, only returned once on creation" }
}
},
"CalendarMember": {
"type": "object",
"required": ["user_id", "email", "role"],
"properties": {
"user_id": { "type": "string", "format": "uuid" },
"email": { "type": "string", "format": "email" },
"role": { "type": "string", "enum": ["owner", "editor", "viewer"] }
}
},
"BusyBlock": {
"type": "object",
"required": ["start", "end", "event_id"],
"properties": {
"start": { "type": "string", "format": "date-time" },
"end": { "type": "string", "format": "date-time" },
"event_id": { "type": "string", "format": "uuid" }
}
},
"WorkingHourSlot": {
"type": "object",
"required": ["start", "end"],
"properties": {
"start": { "type": "string", "example": "09:00", "pattern": "^\\d{2}:\\d{2}$" },
"end": { "type": "string", "example": "17:00", "pattern": "^\\d{2}:\\d{2}$" }
}
},
"TimeSlot": {
"type": "object",
"required": ["start", "end"],
"properties": {
"start": { "type": "string", "format": "date-time" },
"end": { "type": "string", "format": "date-time" }
}
}
}
}
}

View File

@@ -0,0 +1,76 @@
{
"paths": {
"/users/me": {
"get": {
"tags": ["Users"],
"summary": "Get current user profile",
"description": "Returns the full profile of the authenticated user.",
"operationId": "getUserProfile",
"responses": {
"200": {
"description": "User profile",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["user"],
"properties": {
"user": { "$ref": "#/components/schemas/User" }
}
}
}
}
},
"401": { "description": "Not authenticated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
}
},
"put": {
"tags": ["Users"],
"summary": "Update current user profile",
"description": "Updates the authenticated user's profile fields such as timezone.",
"operationId": "updateUserProfile",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"timezone": { "type": "string", "example": "America/Asuncion", "description": "IANA timezone name" }
}
}
}
}
},
"responses": {
"200": {
"description": "Updated user",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["user"],
"properties": {
"user": { "$ref": "#/components/schemas/User" }
}
}
}
}
},
"400": { "description": "Validation error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"401": { "description": "Not authenticated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
}
},
"delete": {
"tags": ["Users"],
"summary": "Delete current user (soft delete)",
"description": "Soft-deletes the authenticated user account along with all associated calendars, events, contacts, and revokes all API keys.",
"operationId": "deleteUser",
"responses": {
"200": { "description": "User deleted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/OkResponse" } } } },
"401": { "description": "Not authenticated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
}
}
}
}
}