{
  "openapi": "3.0.3",
  "info": {
    "title": "Custom APIs — timesheet demo",
    "version": "1.0.0",
    "description": "You are seeing the PUBLIC view: every route this API exposes, whether or not you could call it. To see **your own** documentation — only the routes your key can call — append `?key=<your key>` to this URL, or send it as a bearer token. Get a key from `GET /keys`; it is public and needs no signup. The two views are worth diffing: what disappears is what that key is not allowed to do.\n\nGenerated from the live route registry on every request."
  },
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "paths": {
    "/timesheets": {
      "get": {
        "operationId": "search_timesheets",
        "summary": "SEARCH timesheets",
        "tags": [
          "timesheets"
        ],
        "responses": {
          "200": {
            "description": "Search results with pagination meta (§4.11, §8.4).",
            "headers": {
              "x-api-request-id": {
                "$ref": "#/components/headers/RequestId"
              },
              "x-ua-request-id": {
                "$ref": "#/components/headers/ClientRequestIdEcho"
              },
              "cache-control": {
                "$ref": "#/components/headers/CacheControl"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/SuccessEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/timesheets"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/PermissionDenied"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "description": "List the timesheets in your read scope. `costRate` appears only if you can read it — otherwise the field is absent, not null.\n\nFilter by the query params below: `?field=value` matches equality; other operators use bracket syntax `?field[op]=value` (e.g. `?status[in]=a,b`). Each filterable field lists its operators. Sort with `?sort=field` (`-field` for descending). Page with `?limit` / `?offset`; add `?includeTotalCount=true` for `totalItems` + `totalPages` (§8.4). Owner-scoped: rows are private to their owner — returns only rows the caller owns; holders of `customapis_admin_timesheets` see all owners within their tenant scope. Permissions this route consults to shape the response (they do not gate access, context-can spec §2.4): `customapis_read_cost_rate` — See labour cost rates on timesheets.",
        "x-consulted-permissions": [
          {
            "permission": "customapis_read_cost_rate",
            "description": "See labour cost rates on timesheets"
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ClientRequestId"
          },
          {
            "$ref": "#/components/parameters/limit"
          },
          {
            "$ref": "#/components/parameters/offset"
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "description": "Sort by one of: id, employeeId, workDate, startAt, endAt, hours, status, note, ownerId, ownerDisplayName, createdById, createdByDisplayName, createdAt, lastUpdatedById, lastUpdatedByDisplayName, lastUpdatedAt; `-` prefix = desc, comma-separate for tiebreaks. Any other field is a 400 — this route restricts sorting (§8.4). The unique `id` is always appended for deterministic paging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "employeeId",
            "in": "query",
            "required": false,
            "description": "Filter by employeeId. Operators: eq, ne, in, notIn, isnull, notnull, gt, gte, lt, lte.",
            "schema": {
              "type": "integer",
              "format": "int64"
            }
          },
          {
            "name": "workDate",
            "in": "query",
            "required": false,
            "description": "Filter by workDate. Operators: eq, ne, in, notIn, isnull, notnull, gt, gte, lt, lte.",
            "schema": {
              "description": "Accepts a calendar date `2026-08-12`, or a date-time with an offset which is truncated to its UTC date. Rendered as `YYYY-MM-DD`.",
              "anyOf": [
                {
                  "type": "string",
                  "format": "date"
                },
                {
                  "type": "string",
                  "format": "date-time"
                }
              ]
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Filter by status. Operators: eq, ne, in, notIn, isnull, notnull, like.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "organisationId",
            "in": "query",
            "required": false,
            "description": "Narrows the result to exactly this tenant, within your access scope (never widens it) — a tenant outside your scope returns an empty page, never a 403/404 (search-tenant-narrowing spec §2).",
            "schema": {
              "type": "integer"
            }
          }
        ]
      },
      "post": {
        "operationId": "create_timesheets",
        "summary": "CREATE timesheets",
        "tags": [
          "timesheets"
        ],
        "responses": {
          "201": {
            "description": "Created (§4.11).",
            "headers": {
              "x-api-request-id": {
                "$ref": "#/components/headers/RequestId"
              },
              "x-ua-request-id": {
                "$ref": "#/components/headers/ClientRequestIdEcho"
              },
              "cache-control": {
                "$ref": "#/components/headers/CacheControl"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/SuccessEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/timesheets"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/PermissionDenied"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "description": "Submit a timesheet. The `employeeId` must be in your write scope — the Scratch Sandbox here — or you get a 404; `GET /me` names one your key can use. `costRate` appears only if you can read it — otherwise the field is absent, not null. Owner-scoped: rows are private to their owner — `owner_id` is server-stamped to the caller on create; holders of `customapis_admin_timesheets` see all owners within their tenant scope. Permissions this route consults to shape the response (they do not gate access, context-can spec §2.4): `customapis_read_cost_rate` — See labour cost rates on timesheets.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "employeeId": {
                    "type": "integer"
                  },
                  "workDate": {
                    "description": "Accepts a calendar date `2026-08-12`, or a date-time with an offset which is truncated to its UTC date. Rendered as `YYYY-MM-DD`.",
                    "anyOf": [
                      {
                        "type": "string",
                        "format": "date"
                      },
                      {
                        "type": "string",
                        "format": "date-time"
                      }
                    ]
                  },
                  "startAt": {
                    "description": "Accepts a date-time with an offset `2026-08-12T07:00:00Z` or `2026-08-12T07:00:00+10:00`, or a bare date `2026-08-12` (midnight UTC). A date-time with NO offset (`2026-08-12T07:00` — the value an `<input type=\"datetime-local\">` produces) is a 400: UTC, server-local and caller-local are three different instants. Convert with `new Date(v).toISOString()`. Rendered as ISO 8601 UTC with milliseconds.",
                    "anyOf": [
                      {
                        "type": "string",
                        "format": "date-time"
                      },
                      {
                        "type": "string",
                        "format": "date"
                      }
                    ]
                  },
                  "endAt": {
                    "description": "Accepts a date-time with an offset `2026-08-12T07:00:00Z` or `2026-08-12T07:00:00+10:00`, or a bare date `2026-08-12` (midnight UTC). A date-time with NO offset (`2026-08-12T07:00` — the value an `<input type=\"datetime-local\">` produces) is a 400: UTC, server-local and caller-local are three different instants. Convert with `new Date(v).toISOString()`. Rendered as ISO 8601 UTC with milliseconds.",
                    "anyOf": [
                      {
                        "type": "string",
                        "format": "date-time"
                      },
                      {
                        "type": "string",
                        "format": "date"
                      }
                    ]
                  },
                  "hours": {
                    "type": "number"
                  },
                  "status": {
                    "enum": [
                      "draft",
                      "submitted"
                    ],
                    "type": "string",
                    "description": "Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted. Optional — the database defaults this to \"draft\" when omitted."
                  },
                  "note": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                },
                "required": [
                  "employeeId",
                  "workDate",
                  "startAt",
                  "endAt",
                  "hours"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "x-consulted-permissions": [
          {
            "permission": "customapis_read_cost_rate",
            "description": "See labour cost rates on timesheets"
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ClientRequestId"
          }
        ]
      }
    },
    "/timesheets/{id}": {
      "get": {
        "operationId": "get_timesheets_id",
        "summary": "GET timesheets",
        "tags": [
          "timesheets"
        ],
        "responses": {
          "200": {
            "description": "OK (§4.11).",
            "headers": {
              "x-api-request-id": {
                "$ref": "#/components/headers/RequestId"
              },
              "x-ua-request-id": {
                "$ref": "#/components/headers/ClientRequestIdEcho"
              },
              "cache-control": {
                "$ref": "#/components/headers/CacheControl"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/SuccessEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/timesheets"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/PermissionDenied"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "description": "One timesheet by id. `costRate` appears only if you can read it — otherwise the field is absent, not null. Owner-scoped: rows are private to their owner — another user's row is 404 (never 403); holders of `customapis_admin_timesheets` see all owners within their tenant scope. Permissions this route consults to shape the response (they do not gate access, context-can spec §2.4): `customapis_read_cost_rate` — See labour cost rates on timesheets.",
        "x-consulted-permissions": [
          {
            "permission": "customapis_read_cost_rate",
            "description": "See labour cost rates on timesheets"
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/id"
          },
          {
            "$ref": "#/components/parameters/ClientRequestId"
          }
        ]
      }
    },
    "/timesheets/{id}/approve": {
      "post": {
        "operationId": "update_timesheets_id_approve",
        "summary": "UPDATE timesheets",
        "tags": [
          "timesheets"
        ],
        "responses": {
          "200": {
            "description": "OK (§4.11).",
            "headers": {
              "x-api-request-id": {
                "$ref": "#/components/headers/RequestId"
              },
              "x-ua-request-id": {
                "$ref": "#/components/headers/ClientRequestIdEcho"
              },
              "cache-control": {
                "$ref": "#/components/headers/CacheControl"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/SuccessEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/timesheets"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/PermissionDenied"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "description": "Approve a submitted timesheet. Needs a written reason in `auditMessage`, which lands in the audit trail. Approving twice is a 409; approving outside your write scope — the Scratch Sandbox here — is a 404. `costRate` appears only if you can read it — otherwise the field is absent, not null. Tenant-immutable fields (read-only on PATCH, §9.4): employeeId. Owner-scoped: rows are private to their owner — another user's row is 404 (never 403); holders of `customapis_admin_timesheets` see all owners within their tenant scope. Permissions this route consults to shape the response (they do not gate access, context-can spec §2.4): `customapis_read_cost_rate` — See labour cost rates on timesheets.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "auditMessage": {
                    "type": "string",
                    "description": "Required justification, recorded as the audit entry’s user message (§10.4)."
                  }
                },
                "required": [
                  "auditMessage"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "x-consulted-permissions": [
          {
            "permission": "customapis_read_cost_rate",
            "description": "See labour cost rates on timesheets"
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/id"
          },
          {
            "$ref": "#/components/parameters/ClientRequestId"
          }
        ]
      }
    },
    "/employees": {
      "get": {
        "operationId": "search_employees",
        "summary": "SEARCH employees",
        "tags": [
          "employees"
        ],
        "responses": {
          "200": {
            "description": "Search results with pagination meta (§4.11, §8.4).",
            "headers": {
              "x-api-request-id": {
                "$ref": "#/components/headers/RequestId"
              },
              "x-ua-request-id": {
                "$ref": "#/components/headers/ClientRequestIdEcho"
              },
              "cache-control": {
                "$ref": "#/components/headers/CacheControl"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/SuccessEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/employees"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/PermissionDenied"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "description": "Filter by the query params below: `?field=value` matches equality; other operators use bracket syntax `?field[op]=value` (e.g. `?status[in]=a,b`). Each filterable field lists its operators. Sort with `?sort=field` (`-field` for descending). Page with `?limit` / `?offset`; add `?includeTotalCount=true` for `totalItems` + `totalPages` (§8.4).",
        "parameters": [
          {
            "$ref": "#/components/parameters/ClientRequestId"
          },
          {
            "$ref": "#/components/parameters/limit"
          },
          {
            "$ref": "#/components/parameters/offset"
          },
          {
            "$ref": "#/components/parameters/sort"
          },
          {
            "name": "departmentId",
            "in": "query",
            "required": false,
            "description": "Filter by departmentId. Operators: eq, ne, in, notIn, isnull, notnull, gt, gte, lt, lte.",
            "schema": {
              "type": "integer",
              "format": "int64"
            }
          },
          {
            "name": "fullName",
            "in": "query",
            "required": false,
            "description": "Filter by fullName. Operators: eq, ne, in, notIn, isnull, notnull, like.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "organisationId",
            "in": "query",
            "required": false,
            "description": "Narrows the result to exactly this tenant, within your access scope (never widens it) — a tenant outside your scope returns an empty page, never a 403/404 (search-tenant-narrowing spec §2).",
            "schema": {
              "type": "integer"
            }
          }
        ]
      }
    },
    "/employees/{id}": {
      "get": {
        "operationId": "get_employees_id",
        "summary": "GET employees",
        "tags": [
          "employees"
        ],
        "responses": {
          "200": {
            "description": "OK (§4.11).",
            "headers": {
              "x-api-request-id": {
                "$ref": "#/components/headers/RequestId"
              },
              "x-ua-request-id": {
                "$ref": "#/components/headers/ClientRequestIdEcho"
              },
              "cache-control": {
                "$ref": "#/components/headers/CacheControl"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/SuccessEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/employees"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/PermissionDenied"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/id"
          },
          {
            "$ref": "#/components/parameters/ClientRequestId"
          }
        ]
      }
    },
    "/invoices": {
      "get": {
        "operationId": "search_invoices",
        "summary": "SEARCH invoices",
        "tags": [
          "invoices"
        ],
        "responses": {
          "200": {
            "description": "Search results with pagination meta (§4.11, §8.4).",
            "headers": {
              "x-api-request-id": {
                "$ref": "#/components/headers/RequestId"
              },
              "x-ua-request-id": {
                "$ref": "#/components/headers/ClientRequestIdEcho"
              },
              "cache-control": {
                "$ref": "#/components/headers/CacheControl"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/SuccessEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/invoices"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/PermissionDenied"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "description": "Filter by the query params below: `?field=value` matches equality; other operators use bracket syntax `?field[op]=value` (e.g. `?status[in]=a,b`). Each filterable field lists its operators. Sort with `?sort=field` (`-field` for descending). Page with `?limit` / `?offset`; add `?includeTotalCount=true` for `totalItems` + `totalPages` (§8.4).",
        "parameters": [
          {
            "$ref": "#/components/parameters/ClientRequestId"
          },
          {
            "$ref": "#/components/parameters/limit"
          },
          {
            "$ref": "#/components/parameters/offset"
          },
          {
            "$ref": "#/components/parameters/sort"
          },
          {
            "name": "departmentId",
            "in": "query",
            "required": false,
            "description": "Filter by departmentId. Operators: eq, ne, in, notIn, isnull, notnull, gt, gte, lt, lte.",
            "schema": {
              "type": "integer",
              "format": "int64"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Filter by status. Operators: eq, ne, in, notIn, isnull, notnull, like.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "organisationId",
            "in": "query",
            "required": false,
            "description": "Narrows the result to exactly this tenant, within your access scope (never widens it) — a tenant outside your scope returns an empty page, never a 403/404 (search-tenant-narrowing spec §2).",
            "schema": {
              "type": "integer"
            }
          }
        ]
      }
    },
    "/invoices/{id}": {
      "get": {
        "operationId": "get_invoices_id",
        "summary": "GET invoices",
        "tags": [
          "invoices"
        ],
        "responses": {
          "200": {
            "description": "OK (§4.11).",
            "headers": {
              "x-api-request-id": {
                "$ref": "#/components/headers/RequestId"
              },
              "x-ua-request-id": {
                "$ref": "#/components/headers/ClientRequestIdEcho"
              },
              "cache-control": {
                "$ref": "#/components/headers/CacheControl"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/SuccessEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/invoices"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/PermissionDenied"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/id"
          },
          {
            "$ref": "#/components/parameters/ClientRequestId"
          }
        ]
      }
    },
    "/activity": {
      "get": {
        "operationId": "search_activity",
        "summary": "SEARCH pgrm_audit_log",
        "tags": [
          "pgrm_audit_log"
        ],
        "responses": {
          "200": {
            "description": "Search results with pagination meta (§4.11, §8.4).",
            "headers": {
              "x-api-request-id": {
                "$ref": "#/components/headers/RequestId"
              },
              "x-ua-request-id": {
                "$ref": "#/components/headers/ClientRequestIdEcho"
              },
              "cache-control": {
                "$ref": "#/components/headers/CacheControl"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/SuccessEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/pgrm_audit_log"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/PermissionDenied"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "description": "Every committed change in your company, newest first: who, what, when, the field-level diff, and the written reason where one was required.\n\nFilter by the query params below: `?field=value` matches equality; other operators use bracket syntax `?field[op]=value` (e.g. `?status[in]=a,b`). Each filterable field lists its operators. Sort with `?sort=field` (`-field` for descending). Page with `?limit` / `?offset`; add `?includeTotalCount=true` for `totalItems` + `totalPages` (§8.4).",
        "parameters": [
          {
            "$ref": "#/components/parameters/ClientRequestId"
          },
          {
            "$ref": "#/components/parameters/limit"
          },
          {
            "$ref": "#/components/parameters/offset"
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "description": "Sort by one of: id, createdAt; `-` prefix = desc, comma-separate for tiebreaks. Any other field is a 400 — this route restricts sorting (§8.4). The unique `id` is always appended for deterministic paging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "entityType",
            "in": "query",
            "required": false,
            "description": "Filter by entityType. Operators: eq, ne, in, notIn, isnull, notnull, like.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "entityId",
            "in": "query",
            "required": false,
            "description": "Filter by entityId. Operators: eq, ne, in, notIn, isnull, notnull, gt, gte, lt, lte.",
            "schema": {
              "type": "integer",
              "format": "int64"
            }
          },
          {
            "name": "action",
            "in": "query",
            "required": false,
            "description": "Filter by action. Operators: eq, ne, in, notIn, isnull, notnull, like.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "actorId",
            "in": "query",
            "required": false,
            "description": "Filter by actorId. Operators: eq, ne, in, notIn, isnull, notnull, gt, gte, lt, lte.",
            "schema": {
              "type": "integer",
              "format": "int64"
            }
          },
          {
            "name": "correlationId",
            "in": "query",
            "required": false,
            "description": "Filter by correlationId. Operators: eq, ne, in, notIn, isnull, notnull, like.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "tenantId",
            "in": "query",
            "required": false,
            "description": "Narrows the result to exactly this tenant, within your access scope (never widens it) — a tenant outside your scope returns an empty page, never a 403/404 (search-tenant-narrowing spec §2).",
            "schema": {
              "type": "integer"
            }
          }
        ]
      }
    },
    "/me": {
      "get": {
        "summary": "Who this key is, what it can reach, and what it will be refused",
        "description": "The `cannot` array is a pre-commitment: each entry names an outcome and the status code to expect. Attempt them — that is what the demo is for.",
        "responses": {
          "200": {
            "description": "Identity, scope, permissions, and refusals to expect"
          }
        }
      }
    },
    "/challenges": {
      "get": {
        "summary": "Seven read challenges and a seven-step write sequence. Public",
        "description": "Each step names the persona, the method, the path, the body where there is one, and the status to expect. The reads are repeatable in any order; the write sequence is ordered and mints its own row at step 1, so it is repeatable too. This is the walkthrough the demo is scored on.",
        "security": [],
        "responses": {
          "200": {
            "description": "`reads` and `writeSequence`, each step with its expected status"
          }
        }
      }
    },
    "/keys": {
      "get": {
        "summary": "The current demo key set. Public — start here",
        "description": "Keys rotate every two hours on clock-aligned boundaries; the previous set keeps working for 15 minutes past one. No signup, no credential required.",
        "security": [],
        "responses": {
          "200": {
            "description": "The current window and one key per persona"
          }
        }
      }
    },
    "/events/recent": {
      "get": {
        "summary": "Recent domain events — what a subscriber would have received",
        "description": "Emitted after commit. A rolled-back transaction never appears here.",
        "security": [],
        "responses": {
          "200": {
            "description": "The most recent events, newest first"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "ResponseMeta": {
        "type": "object",
        "properties": {
          "requestId": {
            "type": "string"
          },
          "page": {
            "$ref": "#/components/schemas/PageMeta"
          }
        },
        "required": [
          "requestId"
        ]
      },
      "PageMeta": {
        "type": "object",
        "description": "SEARCH pagination meta (§4.11, §8.4).",
        "properties": {
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer"
          },
          "total": {
            "type": "integer",
            "description": "Total matching rows — included BY DEFAULT (a route may opt out) (§8.4)."
          },
          "pageCount": {
            "type": "integer",
            "description": "Page count — present alongside total (§8.4)."
          }
        },
        "required": [
          "limit",
          "offset"
        ]
      },
      "SuccessEnvelope": {
        "type": "object",
        "description": "Success envelope base (§4.11): carries `meta`; each operation composes its `data` over this via `allOf`.",
        "properties": {
          "meta": {
            "$ref": "#/components/schemas/ResponseMeta"
          }
        },
        "required": [
          "data",
          "meta"
        ]
      },
      "ErrorEnvelope": {
        "type": "object",
        "description": "Error envelope (§4.11). One shape for every failure.",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "validation_failed",
                  "audit_message_required",
                  "unauthenticated",
                  "payment_required",
                  "permission_denied",
                  "not_found",
                  "conflict",
                  "gone",
                  "payload_too_large",
                  "unprocessable_entity",
                  "precondition_required",
                  "too_many_requests",
                  "internal_error",
                  "service_unavailable"
                ]
              },
              "message": {
                "type": "string"
              },
              "details": {
                "type": "object",
                "description": "Structured, self-correcting detail (§C8.4) — present on many 4xx. For a bad filter/sort/view: `{ param, allowed[], hint }` (and `operator` for a bad operator). For body validation: `{ errors: [{ field, message }], allowed[], hint }`.",
                "properties": {
                  "param": {
                    "type": "string",
                    "description": "The offending parameter/field."
                  },
                  "operator": {
                    "type": "string"
                  },
                  "allowed": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "The legal fields / operators / views to choose from."
                  },
                  "hint": {
                    "type": "string",
                    "description": "The next command to run, e.g. `npx pgrm docs <resource>`."
                  },
                  "errors": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "field": {
                          "type": "string"
                        },
                        "message": {
                          "type": "string"
                        }
                      }
                    }
                  }
                },
                "additionalProperties": true
              },
              "requestId": {
                "type": "string"
              }
            },
            "required": [
              "code",
              "message",
              "requestId"
            ]
          }
        },
        "required": [
          "error"
        ]
      },
      "ManagedColumns": {
        "type": "object",
        "description": "The six reserved managed columns (§16) — read-only.",
        "properties": {
          "createdById": {
            "type": "integer",
            "format": "int64",
            "nullable": true,
            "readOnly": true
          },
          "createdByDisplayName": {
            "type": "string",
            "readOnly": true
          },
          "createdAt": {
            "description": "Accepts a date-time with an offset `2026-08-12T07:00:00Z` or `2026-08-12T07:00:00+10:00`, or a bare date `2026-08-12` (midnight UTC). A date-time with NO offset (`2026-08-12T07:00` — the value an `<input type=\"datetime-local\">` produces) is a 400: UTC, server-local and caller-local are three different instants. Convert with `new Date(v).toISOString()`. Rendered as ISO 8601 UTC with milliseconds.",
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "string",
                "format": "date"
              }
            ],
            "readOnly": true
          },
          "lastUpdatedById": {
            "type": "integer",
            "format": "int64",
            "nullable": true,
            "readOnly": true
          },
          "lastUpdatedByDisplayName": {
            "type": "string",
            "readOnly": true
          },
          "lastUpdatedAt": {
            "description": "Accepts a date-time with an offset `2026-08-12T07:00:00Z` or `2026-08-12T07:00:00+10:00`, or a bare date `2026-08-12` (midnight UTC). A date-time with NO offset (`2026-08-12T07:00` — the value an `<input type=\"datetime-local\">` produces) is a 400: UTC, server-local and caller-local are three different instants. Convert with `new Date(v).toISOString()`. Rendered as ISO 8601 UTC with milliseconds.",
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "string",
                "format": "date"
              }
            ],
            "readOnly": true
          }
        }
      },
      "timesheets": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ManagedColumns"
          },
          {
            "type": "object",
            "properties": {
              "id": {
                "type": "integer",
                "format": "int64"
              },
              "employeeId": {
                "type": "integer",
                "format": "int64"
              },
              "workDate": {
                "description": "Accepts a calendar date `2026-08-12`, or a date-time with an offset which is truncated to its UTC date. Rendered as `YYYY-MM-DD`.",
                "anyOf": [
                  {
                    "type": "string",
                    "format": "date"
                  },
                  {
                    "type": "string",
                    "format": "date-time"
                  }
                ]
              },
              "startAt": {
                "description": "Accepts a date-time with an offset `2026-08-12T07:00:00Z` or `2026-08-12T07:00:00+10:00`, or a bare date `2026-08-12` (midnight UTC). A date-time with NO offset (`2026-08-12T07:00` — the value an `<input type=\"datetime-local\">` produces) is a 400: UTC, server-local and caller-local are three different instants. Convert with `new Date(v).toISOString()`. Rendered as ISO 8601 UTC with milliseconds.",
                "anyOf": [
                  {
                    "type": "string",
                    "format": "date-time"
                  },
                  {
                    "type": "string",
                    "format": "date"
                  }
                ]
              },
              "endAt": {
                "description": "Accepts a date-time with an offset `2026-08-12T07:00:00Z` or `2026-08-12T07:00:00+10:00`, or a bare date `2026-08-12` (midnight UTC). A date-time with NO offset (`2026-08-12T07:00` — the value an `<input type=\"datetime-local\">` produces) is a 400: UTC, server-local and caller-local are three different instants. Convert with `new Date(v).toISOString()`. Rendered as ISO 8601 UTC with milliseconds.",
                "anyOf": [
                  {
                    "type": "string",
                    "format": "date-time"
                  },
                  {
                    "type": "string",
                    "format": "date"
                  }
                ]
              },
              "hours": {
                "type": "number"
              },
              "status": {
                "type": "string"
              },
              "costRate": {
                "type": "number",
                "nullable": true
              },
              "note": {
                "type": "string",
                "nullable": true
              },
              "ownerId": {
                "type": "integer",
                "format": "int64",
                "nullable": true,
                "readOnly": true,
                "description": "Owner of this row — server-set to the creating user; immutable (owner-scoped rows)."
              },
              "ownerDisplayName": {
                "type": "string",
                "nullable": true,
                "readOnly": true,
                "description": "Owner's display name captured at creation; not updated on later renames (owner-scoped rows)."
              },
              "tenantId": {
                "type": "integer",
                "readOnly": true,
                "description": "The tenant (organisation root) this row belongs to — server-set on every scoped response so a caller spanning tenants can tell them apart (context-can §2.2)."
              }
            }
          }
        ]
      },
      "employees": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ManagedColumns"
          },
          {
            "type": "object",
            "properties": {
              "id": {
                "type": "integer",
                "format": "int64"
              },
              "departmentId": {
                "type": "integer",
                "format": "int64"
              },
              "fullName": {
                "type": "string"
              },
              "email": {
                "type": "string"
              },
              "userId": {
                "type": "integer",
                "format": "int64",
                "nullable": true
              },
              "tenantId": {
                "type": "integer",
                "readOnly": true,
                "description": "The tenant (organisation root) this row belongs to — server-set on every scoped response so a caller spanning tenants can tell them apart (context-can §2.2)."
              }
            }
          }
        ]
      },
      "invoices": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ManagedColumns"
          },
          {
            "type": "object",
            "properties": {
              "id": {
                "type": "integer",
                "format": "int64"
              },
              "departmentId": {
                "type": "integer",
                "format": "int64"
              },
              "reference": {
                "type": "string"
              },
              "amount": {
                "type": "number"
              },
              "status": {
                "type": "string"
              },
              "tenantId": {
                "type": "integer",
                "readOnly": true,
                "description": "The tenant (organisation root) this row belongs to — server-set on every scoped response so a caller spanning tenants can tell them apart (context-can §2.2)."
              }
            }
          }
        ]
      },
      "pgrm_audit_log": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64",
            "nullable": true
          },
          "tenantId": {
            "type": "integer",
            "format": "int64",
            "nullable": true
          },
          "partitionValue": {
            "type": "integer",
            "format": "int64",
            "nullable": true
          },
          "entityType": {
            "type": "string"
          },
          "entityId": {
            "type": "integer",
            "format": "int64"
          },
          "action": {
            "type": "string"
          },
          "actorId": {
            "type": "integer",
            "format": "int64",
            "nullable": true
          },
          "actorDisplayName": {
            "type": "string"
          },
          "changes": {
            "type": "object",
            "additionalProperties": true
          },
          "systemMessage": {
            "type": "string"
          },
          "userMessage": {
            "type": "string",
            "nullable": true
          },
          "correlationId": {
            "type": "string"
          },
          "userAgentRequestId": {
            "type": "string",
            "nullable": true
          },
          "context": {
            "type": "object",
            "additionalProperties": true
          },
          "createdAt": {
            "description": "Accepts a date-time with an offset `2026-08-12T07:00:00Z` or `2026-08-12T07:00:00+10:00`, or a bare date `2026-08-12` (midnight UTC). A date-time with NO offset (`2026-08-12T07:00` — the value an `<input type=\"datetime-local\">` produces) is a 400: UTC, server-local and caller-local are three different instants. Convert with `new Date(v).toISOString()`. Rendered as ISO 8601 UTC with milliseconds.",
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "string",
                "format": "date"
              }
            ],
            "readOnly": true
          }
        }
      }
    },
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Keys are public — `GET /keys` returns one per persona, no signup — and rotate every 2 hours on clock-aligned boundaries, with a 15-minute grace for the previous set. A 401 means the key aged out: fetch `/keys` again."
      }
    },
    "parameters": {
      "limit": {
        "name": "limit",
        "in": "query",
        "required": false,
        "description": "Page size. Default 25, max 100 (framework hard max 1000) (§8.4).",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 100
        }
      },
      "offset": {
        "name": "offset",
        "in": "query",
        "required": false,
        "description": "Row offset for pagination (§8.4).",
        "schema": {
          "type": "integer",
          "minimum": 0
        }
      },
      "sort": {
        "name": "sort",
        "in": "query",
        "required": false,
        "description": "Sort by a visible root field; `-` prefix = desc. The unique `id` is always appended for deterministic paging (§8.4).",
        "schema": {
          "type": "string"
        }
      },
      "id": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "Path parameter `id`.",
        "schema": {
          "type": "integer",
          "format": "int64"
        }
      },
      "ClientRequestId": {
        "name": "x-ua-request-id",
        "in": "header",
        "required": false,
        "description": "Your own correlation id (§4.1) — optional, echoed back on the response so you can match a response to the call you made. Invalid or over-long values are DROPPED SILENTLY (never a 400, never an echo), and it is never used for authorization.",
        "schema": {
          "type": "string",
          "pattern": "^[A-Za-z0-9._:-]{1,128}$"
        }
      }
    },
    "responses": {
      "ValidationFailed": {
        "description": "Validation failed — bad body, filter, or param (§4.11).",
        "headers": {
          "x-api-request-id": {
            "$ref": "#/components/headers/RequestId"
          },
          "x-ua-request-id": {
            "$ref": "#/components/headers/ClientRequestIdEcho"
          },
          "cache-control": {
            "$ref": "#/components/headers/CacheControl"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "Unauthenticated": {
        "description": "Unauthenticated (§4.11).",
        "headers": {
          "x-api-request-id": {
            "$ref": "#/components/headers/RequestId"
          },
          "x-ua-request-id": {
            "$ref": "#/components/headers/ClientRequestIdEcho"
          },
          "cache-control": {
            "$ref": "#/components/headers/CacheControl"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "PermissionDenied": {
        "description": "Permission denied — empty scope (§9.3).",
        "headers": {
          "x-api-request-id": {
            "$ref": "#/components/headers/RequestId"
          },
          "x-ua-request-id": {
            "$ref": "#/components/headers/ClientRequestIdEcho"
          },
          "cache-control": {
            "$ref": "#/components/headers/CacheControl"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "NotFound": {
        "description": "Not found, cross-tenant, or out-of-scope — indistinguishable by design, including an out-of-scope parent FK on POST (§9.3).",
        "headers": {
          "x-api-request-id": {
            "$ref": "#/components/headers/RequestId"
          },
          "x-ua-request-id": {
            "$ref": "#/components/headers/ClientRequestIdEcho"
          },
          "cache-control": {
            "$ref": "#/components/headers/CacheControl"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "Conflict": {
        "description": "Uniqueness / FK conflict, auto-translated (§4.7).",
        "headers": {
          "x-api-request-id": {
            "$ref": "#/components/headers/RequestId"
          },
          "x-ua-request-id": {
            "$ref": "#/components/headers/ClientRequestIdEcho"
          },
          "cache-control": {
            "$ref": "#/components/headers/CacheControl"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "PayloadTooLarge": {
        "description": "Payload too large (§4.2).",
        "headers": {
          "x-api-request-id": {
            "$ref": "#/components/headers/RequestId"
          },
          "x-ua-request-id": {
            "$ref": "#/components/headers/ClientRequestIdEcho"
          },
          "cache-control": {
            "$ref": "#/components/headers/CacheControl"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "InternalError": {
        "description": "Unexpected error (§4.11).",
        "headers": {
          "x-api-request-id": {
            "$ref": "#/components/headers/RequestId"
          },
          "x-ua-request-id": {
            "$ref": "#/components/headers/ClientRequestIdEcho"
          },
          "cache-control": {
            "$ref": "#/components/headers/CacheControl"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      }
    },
    "headers": {
      "RequestId": {
        "description": "Server request id — echoes `meta.requestId`; also the logs’ requestId and the audit `correlation_id`. Correlate a response with server logs via this value (§4.1).",
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "ClientRequestIdEcho": {
        "description": "Echo of the caller’s `x-ua-request-id` (§4.1). Present ONLY when a valid one was sent — an absent or malformed id is dropped silently, so no echo does not mean no request id. The server’s own `x-api-request-id` is always present regardless.",
        "schema": {
          "type": "string"
        }
      },
      "CacheControl": {
        "description": "Caching policy. `no-store` on every response by default — responses are tenant-scoped and permission-filtered, and the credential is configurable, so the RFC 9111 §3.5 `Authorization` exemption cannot be relied on. A route that is genuinely cacheable overrides this value.",
        "schema": {
          "type": "string"
        }
      }
    }
  }
}