{
  "openapi": "3.1.0",
  "info": {
    "title": "Paņem brīvdienu API",
    "version": "1",
    "summary": "Latvian public holidays, time-off planning and annual leave estimates.",
    "description": "Calendars cover 2026 and 2027 and assume a Monday-to-Friday workweek. The API is open without a key: anonymous clients get 10 requests per second with a burst of 20. An API key (request one through the site's contact form) raises that to 100 per second with a burst of 200. JSON request bodies are limited to 64 KiB.",
    "contact": {
      "name": "SIA Valksor",
      "url": "https://panembrivdienu.lv/en#contact"
    },
    "license": {
      "name": "BSD-3-Clause",
      "identifier": "BSD-3-Clause"
    }
  },
  "externalDocs": {
    "description": "API documentation",
    "url": "https://panembrivdienu.lv/api-docs"
  },
  "servers": [
    {
      "url": "https://panembrivdienu.lv"
    }
  ],
  "security": [
    {},
    {
      "apiKey": []
    }
  ],
  "paths": {
    "/api/v1/calendar/{year}/holidays": {
      "get": {
        "operationId": "listHolidays",
        "summary": "List a year's public holidays",
        "description": "Statutory holidays with Latvian and English names, sorted by date. With apply_transfers=true, the 2026 transferred days off are listed too, marked with is_moved_day.",
        "parameters": [
          {
            "$ref": "#/components/parameters/Year"
          },
          {
            "$ref": "#/components/parameters/ApplyTransfers"
          }
        ],
        "responses": {
          "200": {
            "description": "The year's holidays.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HolidaysResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/InvalidAPIKey"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/calendar/{year}/statistics": {
      "get": {
        "operationId": "getCalendarStatistics",
        "summary": "Count a year's working days and days off",
        "parameters": [
          {
            "$ref": "#/components/parameters/Year"
          },
          {
            "$ref": "#/components/parameters/ApplyTransfers"
          }
        ],
        "responses": {
          "200": {
            "description": "The year's day counts.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CalendarStatisticsResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/InvalidAPIKey"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/optimize": {
      "post": {
        "operationId": "optimizeLeave",
        "summary": "Find leave that bridges days off",
        "description": "Lists up to ten breaks, best first. Each break takes every working day between two or more runs of weekends and holidays and joins them, using at most pto_days leave days on its own. Breaks are ranked by days off, then fewer leave days, then date. Leave days are never before today in Latvian time or before employment_start_date, and a break around New Year counts the next year's holidays.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OptimizeRequest"
              },
              "example": {
                "year": 2027,
                "pto_days": 10
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Breaks, best first.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OptimizeResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/InvalidAPIKey"
          },
          "413": {
            "$ref": "#/components/responses/RequestTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/entitlement": {
      "post": {
        "operationId": "estimateEntitlement",
        "summary": "Estimate annual leave entitlement",
        "description": "Estimates annual leave in calendar days excluding statutory holidays (Labor Law section 149). For a first-year employee the figure is an accrual estimate. For employees under 18 the statutory duration is one calendar month; the 30-day figure is only a nominal estimate.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EntitlementRequest"
              },
              "example": {
                "employment_start_date": "2026-01-01",
                "year": 2027,
                "fte_percentage": 0.5,
                "annual_base": 28
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The entitlement estimate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EntitlementResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/InvalidAPIKey"
          },
          "413": {
            "$ref": "#/components/responses/RequestTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/health": {
      "get": {
        "operationId": "getHealth",
        "summary": "Check that the service is up",
        "responses": {
          "200": {
            "description": "The service is up.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["status"],
                  "properties": {
                    "status": {
                      "type": "string",
                      "const": "ok"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/InvalidAPIKey"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "Optional. Raises the rate limit; a supplied but unknown key returns 401."
      }
    },
    "parameters": {
      "Year": {
        "name": "year",
        "in": "path",
        "required": true,
        "schema": {
          "type": "integer",
          "enum": [2026, 2027]
        }
      },
      "ApplyTransfers": {
        "name": "apply_transfers",
        "in": "query",
        "description": "Apply the 2026 workday transfers (January 2 with January 17, June 22 with June 27). They apply to specified state-funded institutions and are opt-in. Only valid for 2026.",
        "schema": {
          "type": "boolean",
          "default": false
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Invalid parameters (VALIDATION_ERROR) or body (INVALID_JSON).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "InvalidAPIKey": {
        "description": "An API key was sent but is not known (INVALID_API_KEY).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "RequestTooLarge": {
        "description": "The request body is over 64 KiB (REQUEST_TOO_LARGE).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "TooManyRequests": {
        "description": "The client's rate limit is used up (RATE_LIMIT_EXCEEDED).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      }
    },
    "schemas": {
      "Date": {
        "type": "string",
        "format": "date",
        "examples": ["2026-06-24"]
      },
      "ErrorResponse": {
        "type": "object",
        "required": ["error"],
        "properties": {
          "error": {
            "type": "object",
            "required": ["code", "message"],
            "properties": {
              "code": {
                "type": "string",
                "examples": ["VALIDATION_ERROR"]
              },
              "message": {
                "type": "string"
              },
              "request_id": {
                "type": "string"
              }
            }
          }
        }
      },
      "Holiday": {
        "type": "object",
        "required": ["date", "name_lv", "name_en", "is_moved_day"],
        "properties": {
          "date": {
            "$ref": "#/components/schemas/Date"
          },
          "name_lv": {
            "type": "string"
          },
          "name_en": {
            "type": "string"
          },
          "is_moved_day": {
            "type": "boolean",
            "description": "A day off from the 2026 workday transfers rather than a statutory holiday."
          }
        }
      },
      "HolidaysResponse": {
        "type": "object",
        "required": ["year", "transfers_applied", "holidays"],
        "properties": {
          "year": {
            "type": "integer"
          },
          "transfers_applied": {
            "type": "boolean"
          },
          "holidays": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Holiday"
            }
          }
        }
      },
      "CalendarStatisticsResponse": {
        "type": "object",
        "required": [
          "year",
          "transfers_applied",
          "total_days",
          "working_days",
          "weekend_days",
          "holidays",
          "moved_working_days",
          "moved_days_off",
          "total_days_off"
        ],
        "properties": {
          "year": {
            "type": "integer"
          },
          "transfers_applied": {
            "type": "boolean"
          },
          "total_days": {
            "type": "integer"
          },
          "working_days": {
            "type": "integer"
          },
          "weekend_days": {
            "type": "integer"
          },
          "holidays": {
            "type": "integer"
          },
          "moved_working_days": {
            "type": "integer"
          },
          "moved_days_off": {
            "type": "integer"
          },
          "total_days_off": {
            "type": "integer",
            "description": "Weekends, holidays and transferred days off."
          }
        }
      },
      "CalendarStatistics": {
        "type": "object",
        "required": [
          "year",
          "total_days",
          "working_days",
          "weekend_days",
          "holidays",
          "moved_working_days",
          "moved_days_off",
          "total_potential_days_off"
        ],
        "properties": {
          "year": {
            "type": "integer"
          },
          "total_days": {
            "type": "integer"
          },
          "working_days": {
            "type": "integer"
          },
          "weekend_days": {
            "type": "integer"
          },
          "holidays": {
            "type": "integer"
          },
          "moved_working_days": {
            "type": "integer"
          },
          "moved_days_off": {
            "type": "integer"
          },
          "total_potential_days_off": {
            "type": "integer",
            "description": "Weekends, holidays and transferred days off."
          }
        }
      },
      "OptimizeRequest": {
        "type": "object",
        "required": ["year", "pto_days"],
        "properties": {
          "year": {
            "type": "integer",
            "enum": [2026, 2027]
          },
          "pto_days": {
            "type": "integer",
            "minimum": 1,
            "maximum": 100,
            "description": "Scheduled working days to take off."
          },
          "apply_transfers": {
            "type": "boolean",
            "default": false,
            "description": "Apply the 2026 workday transfers. Only valid for 2026."
          },
          "constraints": {
            "type": "object",
            "properties": {
              "employment_start_date": {
                "$ref": "#/components/schemas/Date"
              }
            }
          },
          "extra_days_off": {
            "type": "array",
            "maxItems": 100,
            "description": "Days that count as days off on top of public holidays, such as days an employer gives. Dates in 2026 or 2027.",
            "items": {
              "$ref": "#/components/schemas/Date"
            }
          }
        }
      },
      "Opportunity": {
        "type": "object",
        "required": ["start_date", "end_date", "pto_days", "total_days_off", "efficiency", "description"],
        "properties": {
          "start_date": {
            "$ref": "#/components/schemas/Date"
          },
          "end_date": {
            "$ref": "#/components/schemas/Date"
          },
          "pto_days": {
            "type": "array",
            "description": "The leave days to take.",
            "items": {
              "$ref": "#/components/schemas/Date"
            }
          },
          "total_days_off": {
            "type": "integer"
          },
          "efficiency": {
            "type": "number",
            "description": "Days off per leave day."
          },
          "description": {
            "type": "string"
          }
        }
      },
      "OptimizeResponse": {
        "type": "object",
        "required": ["opportunities", "transfers_applied", "statistics"],
        "properties": {
          "opportunities": {
            "type": "array",
            "maxItems": 10,
            "items": {
              "$ref": "#/components/schemas/Opportunity"
            }
          },
          "transfers_applied": {
            "type": "boolean"
          },
          "statistics": {
            "$ref": "#/components/schemas/CalendarStatistics"
          }
        }
      },
      "EntitlementRequest": {
        "type": "object",
        "required": ["employment_start_date", "year", "fte_percentage", "annual_base"],
        "properties": {
          "employment_start_date": {
            "$ref": "#/components/schemas/Date"
          },
          "birth_date": {
            "$ref": "#/components/schemas/Date"
          },
          "year": {
            "type": "integer",
            "minimum": 2020,
            "maximum": 2100
          },
          "fte_percentage": {
            "type": "number",
            "minimum": 0.1,
            "maximum": 1,
            "description": "Workload as a fraction of full time. Part-time work does not shorten the leave period."
          },
          "annual_base": {
            "type": "integer",
            "minimum": 28,
            "maximum": 60,
            "description": "Annual leave in calendar days; the statutory minimum is 28."
          }
        }
      },
      "EntitlementResponse": {
        "type": "object",
        "required": [
          "entitlement_days",
          "unit",
          "is_accrual_estimate",
          "under_18_calendar_month",
          "six_month_threshold_reached_by_year_end",
          "is_under_18",
          "base_used"
        ],
        "properties": {
          "entitlement_days": {
            "type": "number"
          },
          "unit": {
            "type": "string",
            "const": "calendar_days_excluding_holidays"
          },
          "is_accrual_estimate": {
            "type": "boolean",
            "description": "Employment starts on or after January 1 of the requested year, so the figure is accrued."
          },
          "under_18_calendar_month": {
            "type": "boolean",
            "description": "The statutory duration is one calendar month, not the nominal figure."
          },
          "six_month_threshold_reached_by_year_end": {
            "type": "boolean",
            "description": "Six months of continuous work are reached by year end, after which the full annual leave may be requested."
          },
          "is_under_18": {
            "type": "boolean"
          },
          "base_used": {
            "type": "integer"
          }
        }
      }
    }
  }
}
