{
  "openapi": "3.1.1",
  "info": {
    "title": "Timeslice API",
    "version": "1.0.0",
    "summary": "Deterministic tools for people and AI agents.",
    "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://timeslice.usefulweb.app"
    }
  ],
  "paths": {
    "/api/v1/tools": {
      "get": {
        "operationId": "listTools",
        "summary": "Catalog of all tools",
        "responses": {
          "200": {
            "description": "Tool list.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tools/{id}": {
      "get": {
        "operationId": "getTool",
        "summary": "Definition of one tool",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "timeslice-timesheet-summary",
                "timeslice-duration-parser",
                "timeslice-overlap-check"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Tool definition.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "description": "Invalid input (see errors[]).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tools/timeslice-timesheet-summary/calculate": {
      "post": {
        "operationId": "calculateTimesliceTimesheetSummary",
        "summary": "Paste a timesheet: hours per project, task, tag or client, per day and week, billable sums.",
        "description": "Totals a timesheet table (CSV from any time tracker or spreadsheet) by project, task, tag or client over a date range: hours per day and per ISO week, billable and non-billable time, amounts from hourly rates, optional rounding per entry, a stacked bar chart per day and a donut of the shares.\n\nMethodology:\n- Each row becomes one time entry: date + start + end (an end before the start means the entry runs past midnight), date + start + duration, or date + duration alone (such rows are placed one after another from 09:00 so they still add up per day).\n- Entries are cut at midnight and at the range limits, so a night shift counts on both days. Rounding (optional) applies to each entry piece, to the nearest step or always up.\n- Billable amount = rounded billable hours × the row’s rate (else the default rate). Group by tag counts an entry under each of its tags, so tag totals can add up to more than the total.\n- Weeks are ISO 8601 weeks (Monday to Sunday, week 1 contains the first Thursday of the year). Times are wall-clock times: a night with a daylight-saving change counts the clock difference.\n\nA calculation aid, not payroll or tax advice. Check rounding rules against your contract.",
        "tags": [
          "apps"
        ],
        "x-mcp-tool": "timeslice_timesheet_summary",
        "x-owner-app": "timeslice",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TimesliceTimesheetSummaryInput"
              },
              "examples": {
                "example1": {
                  "summary": "Freelance days for two clients",
                  "value": {
                    "entries": "date,start,end,project,client,task,tags,billable,rate,note\n2026-11-02,09:00,12:30,Brand refresh,Acme Bakery,Logo concepts,design,yes,85,Three directions\n2026-11-02,13:30,14:15,Brand refresh,Acme Bakery,Client call,meeting,yes,85,Kick-off\n2026-11-02,14:30,17:00,Shop website,Northwind Bikes,Wireframes,design,yes,70,\n2026-11-03,09:00,12:00,Shop website,Northwind Bikes,Wireframes,design,yes,70,Checkout flow\n2026-11-03,13:00,15:30,Shop website,Northwind Bikes,Product photos,photo,yes,70,\n2026-11-03,15:45,16:30,Studio admin,,Invoices,admin,no,,\n2026-11-04,08:30,11:45,Brand refresh,Acme Bakery,Packaging layout,design,yes,85,",
                    "groupBy": "project",
                    "currency": "EUR"
                  }
                },
                "example2": {
                  "summary": "Study log with durations, by tag",
                  "value": {
                    "entries": "date;duration;project;task;tags\n3.11.2026;1h30;Calculus;Lecture;lecture\n3.11.2026;2h;Calculus;Problem sets;exercise\n4.11.2026;90m;History;Reading;reading\n4.11.2026;2:15;Programming;Project work;exam\n5.11.2026;1.5;History;Essay;writing, exam",
                    "groupBy": "tag",
                    "currency": ""
                  }
                },
                "example3": {
                  "summary": "Billing in 15-minute steps, rounded up, by client",
                  "value": {
                    "entries": "date,start,end,client,task,billable,rate\n2026-11-09,09:07,10:52,Acme Bakery,Logo revisions,yes,85\n2026-11-09,11:03,11:21,Acme Bakery,Phone call,yes,85\n2026-11-09,13:12,15:40,Northwind Bikes,Banner set,yes,70\n2026-11-10,08:55,09:08,Northwind Bikes,Email,yes,70\n2026-11-10,09:10,12:47,Acme Bakery,Menu boards,yes,85",
                    "groupBy": "client",
                    "roundMinutes": "15",
                    "roundMode": "up",
                    "currency": "EUR"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TimesliceTimesheetSummaryResult"
                }
              }
            }
          },
          "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/tools/timeslice-duration-parser/calculate": {
      "post": {
        "operationId": "calculateTimesliceDurationParser",
        "summary": "Turn 1h30, 90m, 1:30, 9-10:30 or 9am to 1pm into h:mm, decimal hours and ISO 8601.",
        "description": "Reads durations and time ranges the way people type them — 1h30, 1 hour 30 minutes, 90m, 1:30, 1,5 h, PT1H30M, 9-10:30, 9am to 1pm, 22:00-02:00, 14:00 + 45m — one per line, and returns each as h:mm, decimal hours and an ISO 8601 duration, with the total and how every line was read.\n\nMethodology:\n- Each line is tried as a time range first (start–end, “to”, “until”, “bis”, or start + duration), then as a duration, then as a single time of day. An end at or before the start means the range ends on the next day.\n- Durations: number + unit tokens (d, h, hr, hour, min, m, s, also German Std/Min) from large to small; a trailing bare number after hours means minutes (1h30); h:mm and h:mm:ss; ISO 8601 PnDTnHnMnS. A bare number up to 10 counts as hours, a larger whole number as minutes.\n- Times: 9, 930, 9:30, 9.30, 9h30, 21h, 9:30 pm, 12 am = 00:00, 12 pm = noon, noon and midnight; 24:00 is the end of the day. A meridiem on the end time also applies to the start when the start has none (9-11am).\n- Decimal hours are rounded to two places for display; totals use exact seconds. Optional rounding applies to each line before the total.",
        "tags": [
          "apps"
        ],
        "x-mcp-tool": "timeslice_duration_parser",
        "x-owner-app": "timeslice",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TimesliceDurationParserInput"
              },
              "examples": {
                "example1": {
                  "summary": "Mixed ways of writing time",
                  "value": {
                    "lines": "1h30\n90m\n2:15\n1,5 h\n9-10:30\n9am to 1pm\n22:00-02:00\n14:00 + 45m"
                  }
                },
                "example2": {
                  "summary": "A paper time card in 12-hour times",
                  "value": {
                    "lines": "8:30am to 12pm\n12:45pm-5:15pm\n6pm for 1h15",
                    "clock": "12"
                  }
                },
                "example3": {
                  "summary": "Tenths of an hour for billing",
                  "value": {
                    "lines": "17m\n1h04\n2 hours 41 minutes\n0:52",
                    "roundMinutes": "6"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TimesliceDurationParserResult"
                }
              }
            }
          },
          "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/tools/timeslice-overlap-check/calculate": {
      "post": {
        "operationId": "calculateTimesliceOverlapCheck",
        "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/workflows": {
      "get": {
        "operationId": "listWorkflows",
        "summary": "Multi-tool workflows",
        "responses": {
          "200": {
            "description": "Workflow list.",
            "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"
                }
              }
            }
          }
        }
      },
      "TimesliceTimesheetSummaryInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "entries": {
            "title": "Timesheet table (CSV with a header row)",
            "description": "Columns (any order, comma, semicolon or tab): date, start, end or duration, project, client, task, tags, billable, rate, note. Durations like 1h30, 90m, 1:30 or 1.5 work too.",
            "type": "string",
            "maxLength": 400000,
            "default": "date,start,end,project,client,task,tags,billable,rate,note\n2026-11-02,09:00,12:30,Brand refresh,Acme Bakery,Logo concepts,design,yes,85,Three directions\n2026-11-02,13:30,14:15,Brand refresh,Acme Bakery,Client call,meeting,yes,85,Kick-off\n2026-11-02,14:30,17:00,Shop website,Northwind Bikes,Wireframes,design,yes,70,\n2026-11-03,09:00,12:00,Shop website,Northwind Bikes,Wireframes,design,yes,70,Checkout flow\n2026-11-03,13:00,15:30,Shop website,Northwind Bikes,Product photos,photo,yes,70,\n2026-11-03,15:45,16:30,Studio admin,,Invoices,admin,no,,\n2026-11-04,08:30,11:45,Brand refresh,Acme Bakery,Packaging layout,design,yes,85,"
          },
          "groupBy": {
            "title": "Group by",
            "type": "string",
            "enum": [
              "project",
              "task",
              "tag",
              "client"
            ],
            "default": "project"
          },
          "from": {
            "title": "From (YYYY-MM-DD, empty = first entry)",
            "type": "string",
            "maxLength": 10,
            "default": ""
          },
          "to": {
            "title": "To (YYYY-MM-DD, empty = last entry)",
            "type": "string",
            "maxLength": 10,
            "default": ""
          },
          "roundMinutes": {
            "title": "Round each entry to",
            "type": "string",
            "enum": [
              "0",
              "1",
              "5",
              "6",
              "10",
              "15",
              "30",
              "60"
            ],
            "default": "0"
          },
          "roundMode": {
            "title": "Rounding",
            "type": "string",
            "enum": [
              "nearest",
              "up"
            ],
            "default": "nearest"
          },
          "defaultRate": {
            "title": "Hourly rate for rows without one",
            "type": "number",
            "minimum": 0,
            "maximum": 1000000,
            "default": 0
          },
          "currency": {
            "title": "Currency code (for example EUR, USD)",
            "type": "string",
            "maxLength": 3,
            "default": "EUR"
          },
          "dateOrder": {
            "title": "Dates like 03/11/2026 are",
            "type": "string",
            "enum": [
              "dmy",
              "mdy"
            ],
            "default": "dmy"
          },
          "billableByDefault": {
            "title": "Rows without a billable column are billable",
            "type": "boolean",
            "default": false
          }
        },
        "required": []
      },
      "TimesliceTimesheetSummaryResult": {
        "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"
              }
            }
          }
        }
      },
      "TimesliceDurationParserInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "lines": {
            "title": "Durations or time ranges (one per line)",
            "description": "Durations: 1h30, 1h 30m, 90m, 1:30, 1.5 (a bare number up to 10 is hours, larger whole numbers are minutes), PT1H30M. Ranges: 9-10:30, 9:00–17:30, 9am to 1pm, 14:00 + 45m.",
            "type": "string",
            "maxLength": 20000,
            "default": "1h30\n90m\n2:15\n1,5 h\n9-10:30\n9am to 1pm\n22:00-02:00\n14:00 + 45m"
          },
          "clock": {
            "title": "Show times as",
            "type": "string",
            "enum": [
              "24",
              "12"
            ],
            "default": "24"
          },
          "roundMinutes": {
            "title": "Round each line to",
            "type": "string",
            "enum": [
              "0",
              "5",
              "6",
              "15",
              "30"
            ],
            "default": "0"
          }
        },
        "required": []
      },
      "TimesliceDurationParserResult": {
        "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"
              }
            }
          }
        }
      },
      "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"
              }
            }
          }
        }
      }
    }
  }
}