{
  "openapi": "3.1.1",
  "info": {
    "title": "Timeslice Timesheet Overlap Check API",
    "version": "1.0.0",
    "summary": "Find time entries that overlap, the hours counted twice and gaps in each day of a timesheet.",
    "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://timesliceoverlapcheck.usefulweb.app"
    }
  ],
  "paths": {
    "/api/v1/calculate": {
      "post": {
        "operationId": "calculate",
        "summary": "Find time entries that overlap, the hours counted twice and gaps in each day of a timesheet.",
        "description": "Checks a timesheet table for entries that overlap each other (time counted twice), shows each overlapping pair with the shared minutes, compares tracked time with the time actually covered per day and, if you like, lists gaps longer than a set number of minutes.\n\nMethodology:\n- Entries are sorted by start; a sweep keeps the entries still running and reports every pair whose times intersect (start of the later one before the end of the earlier one). Touching entries (one ends at 10:00, the next starts at 10:00) do not overlap.\n- Per day, the tracked time is the plain sum of all entries; the covered time is the length of their union. The difference is time counted more than once. Entries over midnight are split at midnight.\n- Gaps are the empty stretches between covered blocks on the same day (before the first and after the last entry do not count).",
        "tags": [
          "apps"
        ],
        "x-mcp-tool": "timeslice_overlap_check",
        "x-owner-app": "timeslice",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TimesliceOverlapCheckInput"
              },
              "examples": {
                "example1": {
                  "summary": "A sprint day with two overlaps and a night shift",
                  "value": {
                    "entries": "date,start,end,task,note\n2026-11-05,09:30,09:45,Stand-up,\n2026-11-05,09:45,12:15,Relevance tests,\n2026-11-05,12:00,12:30,Code review,started before lunch\n2026-11-05,13:15,16:45,Ranking weights,\n2026-11-05,16:30,17:30,Bug fixing,hotfix\n2026-11-06,22:30,01:15,Deploy,over midnight\n2026-11-07,00:30,01:00,Monitoring,"
                  }
                },
                "example2": {
                  "summary": "Same day, gaps of 30 minutes or more",
                  "value": {
                    "entries": "date,start,end,task,note\n2026-11-05,09:30,09:45,Stand-up,\n2026-11-05,09:45,12:15,Relevance tests,\n2026-11-05,12:00,12:30,Code review,started before lunch\n2026-11-05,13:15,16:45,Ranking weights,\n2026-11-05,16:30,17:30,Bug fixing,hotfix\n2026-11-06,22:30,01:15,Deploy,over midnight\n2026-11-07,00:30,01:00,Monitoring,",
                    "minGapMinutes": 30
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TimesliceOverlapCheckResult"
                }
              }
            }
          },
          "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"
                }
              }
            }
          }
        }
      },
      "TimesliceOverlapCheckInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "entries": {
            "title": "Timesheet table (CSV with a header row)",
            "description": "Columns: date, start, end (or duration), task, project, note. An end before the start means the entry runs past midnight.",
            "type": "string",
            "maxLength": 400000,
            "default": "date,start,end,task,note\n2026-11-05,09:30,09:45,Stand-up,\n2026-11-05,09:45,12:15,Relevance tests,\n2026-11-05,12:00,12:30,Code review,started before lunch\n2026-11-05,13:15,16:45,Ranking weights,\n2026-11-05,16:30,17:30,Bug fixing,hotfix\n2026-11-06,22:30,01:15,Deploy,over midnight\n2026-11-07,00:30,01:00,Monitoring,"
          },
          "minGapMinutes": {
            "title": "Also list gaps of at least (minutes, 0 = off)",
            "type": "number",
            "minimum": 0,
            "maximum": 600,
            "default": 0
          },
          "dateOrder": {
            "title": "Dates like 03/11/2026 are",
            "type": "string",
            "enum": [
              "dmy",
              "mdy"
            ],
            "default": "dmy"
          }
        },
        "required": []
      },
      "TimesliceOverlapCheckResult": {
        "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"
          },
          "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"
              }
            }
          }
        }
      }
    }
  }
}