{
	"openapi": "3.1.0",
	"info": {
		"title": "Appointment Booking API",
		"version": "1.0.0",
		"description": "REST API for appointment booking. All endpoints require a Bearer token generated in the admin panel (Admin > Settings > REST API)."
	},
	"servers": [
		{
			"url": "/api",
			"description": "Current installation"
		}
	],
	"security": [
		{ "bearerAuth": [] }
	],
	"paths": {
		"/schedules": {
			"get": {
				"summary": "List schedules",
				"description": "Returns all available appointment schedules.",
				"operationId": "getSchedules",
				"responses": {
					"200": {
						"description": "List of schedules",
						"content": {
							"application/json": {
								"schema": { "$ref": "#/components/schemas/SchedulesResponse" }
							}
						}
					},
					"401": { "$ref": "#/components/responses/Unauthorized" },
					"429": { "$ref": "#/components/responses/TooManyRequests" },
					"503": { "$ref": "#/components/responses/ServiceUnavailable" }
				}
			}
		},
		"/reasons": {
			"get": {
				"summary": "List reasons for a schedule",
				"description": "Returns appointment reasons (services) available for the given schedule.",
				"operationId": "getReasons",
				"parameters": [
					{
						"name": "schedule",
						"in": "query",
						"required": true,
						"description": "Schedule ID",
						"schema": { "type": "integer", "minimum": 1 }
					}
				],
				"responses": {
					"200": {
						"description": "List of reasons. Empty array if no reasons configured.",
						"content": {
							"application/json": {
								"schema": { "$ref": "#/components/schemas/ReasonsResponse" }
							}
						}
					},
					"401": { "$ref": "#/components/responses/Unauthorized" },
					"404": { "$ref": "#/components/responses/NotFound" },
					"429": { "$ref": "#/components/responses/TooManyRequests" }
				}
			}
		},
		"/days": {
			"get": {
				"summary": "List available days",
				"description": "Returns dates that have at least one open slot for the given schedule and reason.",
				"operationId": "getDays",
				"parameters": [
					{
						"name": "schedule",
						"in": "query",
						"required": true,
						"description": "Schedule ID",
						"schema": { "type": "integer", "minimum": 1 }
					},
					{
						"name": "reason",
						"in": "query",
						"required": true,
						"description": "Reason ID",
						"schema": { "type": "integer", "minimum": 1 }
					}
				],
				"responses": {
					"200": {
						"description": "List of available dates in YYYY-MM-DD format",
						"content": {
							"application/json": {
								"schema": { "$ref": "#/components/schemas/DaysResponse" }
							}
						}
					},
					"401": { "$ref": "#/components/responses/Unauthorized" },
					"404": { "$ref": "#/components/responses/NotFound" },
					"429": { "$ref": "#/components/responses/TooManyRequests" }
				}
			}
		},
		"/slots": {
			"get": {
				"summary": "List available time slots",
				"description": "Returns available time slots for a specific date within the given schedule and reason.",
				"operationId": "getSlots",
				"parameters": [
					{
						"name": "schedule",
						"in": "query",
						"required": true,
						"description": "Schedule ID",
						"schema": { "type": "integer", "minimum": 1 }
					},
					{
						"name": "reason",
						"in": "query",
						"required": true,
						"description": "Reason ID",
						"schema": { "type": "integer", "minimum": 1 }
					},
					{
						"name": "day",
						"in": "query",
						"required": true,
						"description": "Date in YYYY-MM-DD format",
						"schema": { "type": "string", "pattern": "^\\d{4}-\\d{2}-\\d{2}$" },
						"example": "2026-05-23"
					}
				],
				"responses": {
					"200": {
						"description": "List of available time slots. Empty array if no slots available on this day.",
						"content": {
							"application/json": {
								"schema": { "$ref": "#/components/schemas/SlotsResponse" }
							}
						}
					},
					"401": { "$ref": "#/components/responses/Unauthorized" },
					"404": { "$ref": "#/components/responses/NotFound" },
					"429": { "$ref": "#/components/responses/TooManyRequests" }
				}
			}
		},
		"/forms": {
			"get": {
				"summary": "Get booking form fields",
				"description": "Returns the form fields that must be submitted when booking the specified slot.",
				"operationId": "getForms",
				"parameters": [
					{
						"name": "schedule",
						"in": "query",
						"required": true,
						"description": "Schedule ID",
						"schema": { "type": "integer", "minimum": 1 }
					},
					{
						"name": "reason",
						"in": "query",
						"required": true,
						"description": "Reason ID",
						"schema": { "type": "integer", "minimum": 1 }
					},
					{
						"name": "slot",
						"in": "query",
						"required": true,
						"description": "Slot timestamp in YYYY-MM-DD HH:MM:SS format (URL-encode the space as %20)",
						"schema": { "type": "string", "pattern": "^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}$" },
						"example": "2026-05-23 09:00:00"
					}
				],
				"responses": {
					"200": {
						"description": "Form field definitions keyed by field name",
						"content": {
							"application/json": {
								"schema": { "$ref": "#/components/schemas/FormsResponse" }
							}
						}
					},
					"401": { "$ref": "#/components/responses/Unauthorized" },
					"404": { "$ref": "#/components/responses/NotFound" },
					"429": { "$ref": "#/components/responses/TooManyRequests" }
				}
			}
		},
		"/bookings": {
			"post": {
				"summary": "Create a booking",
				"description": "Creates a new appointment. Submit form field values from GET /forms in the `submission` object.",
				"operationId": "postBookings",
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": { "$ref": "#/components/schemas/BookingRequest" }
						}
					}
				},
				"responses": {
					"201": {
						"description": "Appointment created",
						"content": {
							"application/json": {
								"schema": { "$ref": "#/components/schemas/BookingResponse" }
							}
						}
					},
					"400": {
						"description": "Invalid request, missing required fields, or invalid JSON",
						"content": {
							"application/json": {
								"schema": { "$ref": "#/components/schemas/BookingError" }
							}
						}
					},
					"401": { "$ref": "#/components/responses/Unauthorized" },
					"404": { "$ref": "#/components/responses/NotFound" },
					"415": {
						"description": "Content-Type must be application/json",
						"content": {
							"application/json": {
								"schema": { "$ref": "#/components/schemas/Error" },
								"example": { "error": "Unsupported Media Type" }
							}
						}
					},
					"429": { "$ref": "#/components/responses/TooManyRequests" },
					"500": {
						"description": "Failed to create user or appointment",
						"content": {
							"application/json": {
								"schema": { "$ref": "#/components/schemas/Error" }
							}
						}
					}
				}
			}
		}
	},
	"components": {
		"securitySchemes": {
			"bearerAuth": {
				"type": "http",
				"scheme": "bearer",
				"description": "Token generated in Admin > Settings > REST API. Send as: Authorization: Bearer <token>"
			}
		},
		"schemas": {
			"Error": {
				"type": "object",
				"required": ["error"],
				"properties": {
					"error": { "type": "string" }
				}
			},
			"BookingError": {
				"type": "object",
				"required": ["error"],
				"properties": {
					"error": { "type": "string" },
					"field": {
						"type": "string",
						"description": "The field name that failed validation, if applicable"
					}
				}
			},
			"Schedule": {
				"type": "object",
				"required": ["id", "name"],
				"properties": {
					"id": { "type": "integer" },
					"name": { "type": "string" }
				}
			},
			"SchedulesResponse": {
				"type": "object",
				"required": ["data"],
				"properties": {
					"data": {
						"type": "array",
						"items": { "$ref": "#/components/schemas/Schedule" }
					}
				}
			},
			"Reason": {
				"type": "object",
				"required": ["id", "name", "description", "duration"],
				"properties": {
					"id": { "type": "integer" },
					"name": { "type": "string" },
					"description": { "type": "string" },
					"duration": {
						"type": "integer",
						"description": "Duration in seconds"
					}
				}
			},
			"ReasonsResponse": {
				"type": "object",
				"required": ["data"],
				"properties": {
					"data": {
						"type": "array",
						"items": { "$ref": "#/components/schemas/Reason" }
					}
				}
			},
			"DaysResponse": {
				"type": "object",
				"required": ["data"],
				"properties": {
					"data": {
						"type": "array",
						"items": {
							"type": "string",
							"description": "Date in YYYY-MM-DD format",
							"pattern": "^\\d{4}-\\d{2}-\\d{2}$"
						},
						"example": ["2026-05-23", "2026-05-24", "2026-05-26"]
					}
				}
			},
			"SlotsResponse": {
				"type": "object",
				"required": ["data"],
				"properties": {
					"data": {
						"type": "array",
						"items": {
							"type": "string",
							"description": "Slot timestamp in YYYY-MM-DD HH:MM:SS format (local time)",
							"pattern": "^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}$"
						},
						"example": ["2026-05-23 09:00:00", "2026-05-23 09:30:00", "2026-05-23 10:00:00"]
					}
				}
			},
			"FormField": {
				"type": "object",
				"required": ["form_type", "input_type", "label", "required", "value"],
				"properties": {
					"form_type": {
						"type": "string",
						"description": "Field category: textbox, cust_form_field, app_form_field, etc."
					},
					"input_type": {
						"type": "string",
						"description": "HTML input type: text, email, tel, textarea, checkbox, select, etc."
					},
					"label": {
						"type": "string",
						"description": "Display label for the field"
					},
					"required": {
						"type": "boolean",
						"description": "Whether the field must be included in the booking submission"
					},
					"value": {
						"description": "Default or current value"
					}
				}
			},
			"FormsResponse": {
				"type": "object",
				"required": ["data"],
				"properties": {
					"data": {
						"type": "object",
						"description": "Form fields keyed by field name",
						"additionalProperties": { "$ref": "#/components/schemas/FormField" },
						"example": {
							"first_name": {
								"form_type": "textbox",
								"input_type": "text",
								"label": "First Name",
								"required": true,
								"value": ""
							},
							"email": {
								"form_type": "textbox",
								"input_type": "email",
								"label": "Email Address",
								"required": true,
								"value": ""
							}
						}
					}
				}
			},
			"BookingRequest": {
				"type": "object",
				"required": ["schedule", "reason", "slot", "submission"],
				"properties": {
					"schedule": {
						"type": "integer",
						"description": "Schedule ID",
						"minimum": 1
					},
					"reason": {
						"type": "integer",
						"description": "Reason ID",
						"minimum": 1
					},
					"slot": {
						"type": "string",
						"description": "Slot timestamp in YYYY-MM-DD HH:MM:SS format (exactly 19 characters)",
						"pattern": "^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}$",
						"example": "2026-05-23 09:00:00"
					},
					"submission": {
						"type": "object",
						"description": "Form field values keyed by field name from GET /forms",
						"additionalProperties": true,
						"example": {
							"first_name": "Jane",
							"last_name": "Doe",
							"email": "jane.doe@example.com"
						}
					}
				}
			},
			"BookingResponse": {
				"type": "object",
				"required": ["booking_id", "user_id", "slot", "booking_details_id"],
				"properties": {
					"booking_id": {
						"type": "integer",
						"description": "Created appointment ID"
					},
					"booking_details_id": {
						"type": "string",
						"description": "Appointment login ID for customer self-service access"
					},
					"user_id": {
						"type": "integer",
						"description": "Customer user ID created during booking"
					},
					"slot": {
						"type": "string",
						"description": "Appointment start time in ISO 8601 UTC format",
						"pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$",
						"example": "2026-05-23T09:00:00Z"
					}
				}
			}
		},
		"responses": {
			"Unauthorized": {
				"description": "Missing or invalid Bearer token",
				"content": {
					"application/json": {
						"schema": { "$ref": "#/components/schemas/Error" },
						"example": { "error": "Unauthorized" }
					}
				}
			},
			"NotFound": {
				"description": "Schedule, reason, day, or slot not found",
				"content": {
					"application/json": {
						"schema": { "$ref": "#/components/schemas/Error" },
						"example": { "error": "Schedule not found" }
					}
				}
			},
			"TooManyRequests": {
				"description": "Rate limit exceeded (default: 300 requests per minute per token)",
				"headers": {
					"Retry-After": {
						"description": "Seconds until the rate-limit window resets",
						"schema": { "type": "integer" }
					}
				},
				"content": {
					"application/json": {
						"schema": { "$ref": "#/components/schemas/Error" },
						"example": { "error": "Too Many Requests" }
					}
				}
			},
			"ServiceUnavailable": {
				"description": "System not configured (database connection file missing)",
				"content": {
					"application/json": {
						"schema": { "$ref": "#/components/schemas/Error" },
						"example": { "error": "Not configured" }
					}
				}
			}
		}
	}
}
