{
  "openapi": "3.1.1",
  "info": {
    "title": "Focus Room Focus Timer Planner API",
    "version": "1.0.0",
    "summary": "Plan Pomodoro, 52/17 or custom focus blocks and export them as a calendar file.",
    "description": "Every endpoint performs the same calculation as the human page and returns inputs, result, units, assumptions, sources, warnings and computedAt. MCP endpoint: /mcp."
  },
  "servers": [
    {
      "url": "https://focusroomfocustimer.usefulweb.app"
    }
  ],
  "paths": {
    "/api/v1/calculate": {
      "post": {
        "operationId": "calculate",
        "summary": "Plan Pomodoro, 52/17 or custom focus blocks and export them as a calendar file.",
        "description": "Turns a focus routine into a concrete schedule: Pomodoro (25-minute blocks, 5-minute breaks, a 15-minute break after every fourth block), 52/17, or your own lengths. Enter a start date and time to get every focus block and break with clock times, the total focus and break time, and an iCalendar (.ics) file with one event per block — optionally with breaks and a reminder — that any calendar app can import. The Focus Room app uses the same planner for its timer and its calendar export.\n\nMethodology:\n- Pomodoro: 25 minutes of focus, 5-minute short breaks and a 15-minute long break after every fourth block (the lengths of Francesco Cirillo’s technique). 52/17: 52 minutes of focus and 17 minutes of break (the ratio DeskTime reported in 2014). Custom uses your lengths.\n- There is no break after the last block. Times are added in whole minutes to the local start; there are no daylight-saving jumps because the calendar file uses floating local times (no time zone), which calendar apps show at the same wall-clock time.\n- The .ics file follows RFC 5545: one VEVENT per focus block (busy) and optionally per break (free), with a display reminder when requested; lines are folded at 75 octets. UIDs and DTSTAMP are derived from the plan, so the same input always gives the same file.\n\nFocus routines are conventions, not medical advice. Take longer breaks whenever you need them.",
        "tags": [
          "apps"
        ],
        "x-mcp-tool": "focusroom_focus_timer",
        "x-owner-app": "focusroom",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FocusroomFocusTimerInput"
              },
              "examples": {
                "example1": {
                  "summary": "Four Pomodoros from 9:00 with a 1-minute reminder",
                  "value": {
                    "mode": "pomodoro",
                    "cycles": 4,
                    "start": "2026-11-16T09:00",
                    "alarmMinutes": 1,
                    "title": "Focus block"
                  }
                },
                "example2": {
                  "summary": "Three 52/17 blocks in the afternoon, breaks included",
                  "value": {
                    "mode": "52-17",
                    "cycles": 3,
                    "start": "2026-11-16T13:30",
                    "includeBreaks": true,
                    "title": "Deep work"
                  }
                },
                "example3": {
                  "summary": "Custom 45 / 10 with a long break after three blocks",
                  "value": {
                    "mode": "custom",
                    "cycles": 6,
                    "focusMinutes": 45,
                    "shortBreakMinutes": 10,
                    "longBreakMinutes": 30,
                    "longEvery": 3,
                    "start": "2026-11-17T08:00",
                    "title": "Study"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FocusroomFocusTimerResult"
                }
              }
            }
          },
          "400": {
            "description": "Malformed JSON body.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Invalid input (see errors[]).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (see Retry-After).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tool": {
      "get": {
        "operationId": "getToolDefinition",
        "summary": "Inputs, output schema and examples for this tool",
        "responses": {
          "200": {
            "description": "Tool definition.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Problem": {
        "type": "object",
        "description": "RFC 9457 problem details. 422 responses include per-field `errors`.",
        "required": [
          "type",
          "title",
          "status",
          "detail"
        ],
        "properties": {
          "type": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "detail": {
            "type": "string"
          },
          "code": {
            "type": "string"
          },
          "errors": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "field",
                "message"
              ],
              "properties": {
                "field": {
                  "type": "string"
                },
                "message": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "FocusroomFocusTimerInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "mode": {
            "title": "Routine",
            "type": "string",
            "enum": [
              "pomodoro",
              "52-17",
              "custom"
            ],
            "default": "pomodoro"
          },
          "cycles": {
            "title": "Focus blocks",
            "type": "integer",
            "minimum": 1,
            "maximum": 16,
            "default": 4
          },
          "start": {
            "title": "Start",
            "description": "Local date and time of the first block. Calendar events use floating local time. Local date-time YYYY-MM-DDTHH:mm",
            "type": "string",
            "default": "2026-11-16T09:00"
          },
          "focusMinutes": {
            "title": "Focus minutes (custom)",
            "type": "integer",
            "minimum": 1,
            "maximum": 180,
            "default": 45,
            "x-unit": "min"
          },
          "shortBreakMinutes": {
            "title": "Short break minutes (custom)",
            "type": "integer",
            "minimum": 1,
            "maximum": 60,
            "default": 10,
            "x-unit": "min"
          },
          "longBreakMinutes": {
            "title": "Long break minutes (custom)",
            "type": "integer",
            "minimum": 1,
            "maximum": 90,
            "default": 20,
            "x-unit": "min"
          },
          "longEvery": {
            "title": "Long break after every n blocks (custom, 0 = never)",
            "type": "integer",
            "minimum": 0,
            "maximum": 12,
            "default": 3
          },
          "includeBreaks": {
            "title": "Put breaks in the calendar file too",
            "type": "boolean",
            "default": false
          },
          "alarmMinutes": {
            "title": "Reminder before each block",
            "description": "0 = no reminder.",
            "type": "integer",
            "minimum": 0,
            "maximum": 60,
            "default": 0,
            "x-unit": "min"
          },
          "title": {
            "title": "Event title",
            "type": "string",
            "maxLength": 120,
            "default": "Focus block"
          }
        },
        "required": []
      },
      "FocusroomFocusTimerResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "schemaVersion",
          "tool",
          "computedAt",
          "inputs",
          "summary",
          "result",
          "units",
          "assumptions",
          "sources",
          "warnings",
          "presentation",
          "links"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "const": "1.0"
          },
          "tool": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "id",
              "name",
              "version"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "version": {
                "type": "string"
              }
            }
          },
          "computedAt": {
            "type": "string",
            "format": "date-time"
          },
          "inputs": {
            "type": "object",
            "description": "Normalised inputs actually used, including applied defaults."
          },
          "summary": {
            "type": "string"
          },
          "result": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "mode",
              "focusMinutes",
              "breakMinutes",
              "totalMinutes",
              "blocks",
              "start",
              "end",
              "phases",
              "icsFile"
            ],
            "properties": {
              "mode": {
                "type": "string"
              },
              "focusMinutes": {
                "type": "number"
              },
              "breakMinutes": {
                "type": "number"
              },
              "totalMinutes": {
                "type": "number"
              },
              "blocks": {
                "type": "integer"
              },
              "start": {
                "type": "string",
                "description": "Local start YYYY-MM-DD HH:MM."
              },
              "end": {
                "type": "string"
              },
              "phases": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "kind",
                    "block",
                    "start",
                    "end",
                    "minutes"
                  ],
                  "properties": {
                    "kind": {
                      "type": "string",
                      "enum": [
                        "focus",
                        "short-break",
                        "long-break"
                      ]
                    },
                    "block": {
                      "type": "integer"
                    },
                    "start": {
                      "type": "string"
                    },
                    "end": {
                      "type": "string"
                    },
                    "minutes": {
                      "type": "number"
                    }
                  }
                }
              },
              "icsFile": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "artifactId",
                  "filename",
                  "mime",
                  "bytes",
                  "sha256",
                  "path"
                ],
                "properties": {
                  "artifactId": {
                    "type": "string"
                  },
                  "filename": {
                    "type": "string"
                  },
                  "mime": {
                    "type": "string"
                  },
                  "bytes": {
                    "type": "integer"
                  },
                  "sha256": {
                    "type": "string"
                  },
                  "path": {
                    "type": "string",
                    "description": "Download path on the same site (valid for about 2 hours)."
                  }
                }
              }
            }
          },
          "units": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "assumptions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "title"
              ],
              "properties": {
                "title": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "publisher": {
                  "type": "string"
                },
                "retrievedAt": {
                  "type": "string"
                },
                "note": {
                  "type": "string"
                }
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "presentation": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Display blocks used by the human page (same data)."
          },
          "links": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "human",
              "api",
              "openapi",
              "methodology"
            ],
            "properties": {
              "human": {
                "type": "string",
                "format": "uri"
              },
              "api": {
                "type": "string",
                "format": "uri"
              },
              "openapi": {
                "type": "string",
                "format": "uri"
              },
              "methodology": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      }
    }
  }
}