{
    "openapi": "3.1.0",
    "info": {
        "title": "plnnrly API",
        "version": "2.8.0",
        "description": "The plnnrly REST API. Every endpoint is served on your organisation's own plnnrly address and reaches only that organisation's data.",
        "contact": {
            "name": "plnnrly support",
            "url": "https://www.plnnrly.com/en/support"
        },
        "license": {
            "name": "Proprietary"
        }
    },
    "servers": [
        {
            "url": "https://{tenant_host}/api/v2",
            "variables": {
                "tenant_host": {
                    "default": "your-tenant.plnnrly.com",
                    "description": "Your plnnrly address, the host you sign in on. The API is only reachable on it."
                }
            }
        }
    ],
    "tags": [
        {
            "name": "Start here",
            "description": "Discovery, the token owner and upload tickets."
        },
        {
            "name": "Projects",
            "description": "Projects, budgets and the customers they are billed to."
        },
        {
            "name": "Work items",
            "description": "The backlog: work items, subtasks, links, comments and attachments."
        },
        {
            "name": "Sprints",
            "description": "Sprint planning and the sprint state machine."
        },
        {
            "name": "Time",
            "description": "Worklogs and timers."
        },
        {
            "name": "Drive",
            "description": "Drive items, documents, folders, notes and todos, and the comments and files on a drive item."
        },
        {
            "name": "Customers",
            "description": "Customers, contacts and categories."
        },
        {
            "name": "Billing",
            "description": "Sales invoices, subscriptions and invoice lines."
        },
        {
            "name": "Leave",
            "description": "Absences, holidays, free days, check-ins and furlough balances."
        },
        {
            "name": "Planning",
            "description": "Calendar events."
        },
        {
            "name": "Organization",
            "description": "Users and company branches."
        },
        {
            "name": "Configuration",
            "description": "Workflows, statuses, work types and labels."
        }
    ],
    "security": [
        {
            "bearerToken": []
        }
    ],
    "paths": {
        "/": {
            "get": {
                "operationId": "api.v2.discovery",
                "summary": "List the resources this API exposes",
                "description": "Returns one entry per resource available to your organisation, with its collection URL, its templated item URL, its permission prefix, the OpenAPI schemas that describe it and the operations it registers. Resources belonging to a feature your organisation does not have are omitted. Not every entry is an endpoint you can call: the list does not depend on your token's scope or your permissions, so a listed operation can still answer `403`, and a `null` `path` or `item_path` means the resource has no such URL. It needs a valid token but no permission.",
                "tags": [
                    "Start here"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/ViewSummary"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "A page of records.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record.",
                                "schema": {
                                    "type": "string",
                                    "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/DiscoveryEntryList"
                                            }
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/DiscoveryMeta"
                                        },
                                        "links": {
                                            "$ref": "#/components/schemas/PageLinks"
                                        }
                                    },
                                    "required": [
                                        "data",
                                        "meta",
                                        "links"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, max-age=300",
                "x-plnnrly-examples": [
                    {
                        "name": "Discover the API"
                    }
                ],
                "x-plnnrly-permission": null,
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:read"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/DiscoveryEntryList",
                    "detail": "#/components/schemas/DiscoveryEntry"
                }
            },
            "x-plnnrly-allow": [
                "GET"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/absences": {
            "get": {
                "operationId": "api.v2.absences.index",
                "summary": "List absences",
                "description": "Returns absences (the periods employees were away from work), newest first by `start`. An absence with a `null` `end` is still running. There is no `year` parameter and no `ongoing` parameter: filter a year as `filter[start][gte]=2026-01-01T00:00:00Z&filter[start][lte]=2026-12-31T23:59:59Z`, and the running absences as `filter[end][null]=true`. There is no `q` parameter either: an absence has no text of its own.",
                "tags": [
                    "Leave"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:absences"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:absences"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/PerPage"
                    },
                    {
                        "$ref": "#/components/parameters/Cursor"
                    },
                    {
                        "$ref": "#/components/parameters/PageNumber"
                    },
                    {
                        "$ref": "#/components/parameters/ViewSummary"
                    },
                    {
                        "name": "filter[created_at]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][gte]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][lt]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[end]",
                        "in": "query",
                        "description": "When the absence ends. `null` selects the absences that are still running.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[end][gte]",
                        "in": "query",
                        "description": "When the absence ends. `null` selects the absences that are still running.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[end][lte]",
                        "in": "query",
                        "description": "When the absence ends. `null` selects the absences that are still running.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[end][null]",
                        "in": "query",
                        "description": "When the absence ends. `null` selects the absences that are still running. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[manager_id]",
                        "in": "query",
                        "description": "Manager who filed or approved the absence. `null` selects absences with no manager recorded.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[manager_id][in]",
                        "in": "query",
                        "description": "Manager who filed or approved the absence. `null` selects absences with no manager recorded. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[manager_id][null]",
                        "in": "query",
                        "description": "Manager who filed or approved the absence. `null` selects absences with no manager recorded. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[start]",
                        "in": "query",
                        "description": "When the absence begins.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[start][gte]",
                        "in": "query",
                        "description": "When the absence begins.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[start][lte]",
                        "in": "query",
                        "description": "When the absence begins.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][gte]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][lt]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[user_id]",
                        "in": "query",
                        "description": "Whose absence this is. Accepts the literal `me`. Naming another user requires the `absences::manage` permission.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[user_id][in]",
                        "in": "query",
                        "description": "Whose absence this is. Accepts the literal `me`. Naming another user requires the `absences::manage` permission. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "$ref": "#/components/parameters/AbsenceSort"
                    },
                    {
                        "$ref": "#/components/parameters/AbsenceInclude"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "A page of records.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record.",
                                "schema": {
                                    "type": "string",
                                    "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/AbsenceList"
                                            }
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/AbsencesIndexMeta"
                                        },
                                        "links": {
                                            "$ref": "#/components/schemas/PageLinks"
                                        }
                                    },
                                    "required": [
                                        "data",
                                        "meta",
                                        "links"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "The absences that are still running",
                        "query": {
                            "filter[end][null]": "true",
                            "include": "user"
                        }
                    },
                    {
                        "name": "One employee's absences in 2026",
                        "query": {
                            "filter[user_id]": "me",
                            "filter[start][gte]": "2026-01-01T00:00:00Z",
                            "filter[start][lte]": "2026-12-31T23:59:59Z"
                        }
                    }
                ],
                "x-plnnrly-permission": "absences::manage",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1,
                    "view_detail_total": 3,
                    "surcharges": {
                        "include": 1
                    }
                },
                "x-plnnrly-token-ability": [
                    "api:read:absences"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/AbsenceList",
                    "detail": "#/components/schemas/Absence"
                }
            },
            "post": {
                "operationId": "api.v2.absences.store",
                "summary": "Book an absence",
                "description": "Books an absence for an employee and returns it with a `Location` header. `start` and `end` are stored exactly as sent: they are RFC 3339 UTC instants and no timezone conversion is applied, unlike the form in plnnrly, which collects local wall-clock time. Omit `end` for an absence that is still running; it then reaches through now everywhere it is read, so close it when the employee returns. Naming a `user_id` or `manager_id` additionally requires read access to users (`users::read`) and a token covering that prefix, because the reference is checked against what the caller may see. Nothing prevents two overlapping absences for one employee.",
                "tags": [
                    "Leave"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:absences"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:absences"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "user_id": {
                                        "type": "integer",
                                        "x-plnnrly-references": {
                                            "resource": "User",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "users",
                                        "description": "The employee this absence is for. Writing it requires the `absences::manage` permission."
                                    },
                                    "manager_id": {
                                        "type": "integer",
                                        "x-plnnrly-references": {
                                            "resource": "User",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "users",
                                        "description": "Manager who filed or approved the absence, or `null` when none was recorded. The foreign key nulls on delete, so removing the manager leaves the absence behind unattributed."
                                    },
                                    "start": {
                                        "type": "string",
                                        "format": "date-time",
                                        "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$",
                                        "description": "When the absence begins. RFC 3339 UTC."
                                    },
                                    "end": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "format": "date-time",
                                        "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$",
                                        "x-plnnrly-constraint": "Must be on or after start.",
                                        "description": "When the absence ends, or `null` while it is still running. An ongoing absence reaches through now everywhere it is read (planning capacity and reporting both count it), and that reach is uncapped, so an absence nobody closed keeps consuming capacity. RFC 3339 UTC."
                                    }
                                },
                                "required": [
                                    "user_id",
                                    "manager_id",
                                    "start"
                                ],
                                "additionalProperties": false
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Created.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "Idempotency-Replayed": {
                                "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise.",
                                "schema": {
                                    "type": "boolean",
                                    "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise."
                                }
                            },
                            "Location": {
                                "description": "The absolute URL of the created or queued resource.",
                                "schema": {
                                    "type": "string",
                                    "description": "The absolute URL of the created or queued resource."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Absence"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "$ref": "#/components/responses/Problem409Idempotency"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "A closed absence",
                        "body": {
                            "user_id": 1,
                            "manager_id": 1,
                            "start": "2026-03-02T08:00:00Z",
                            "end": "2026-03-06T17:00:00Z"
                        }
                    },
                    {
                        "name": "An absence that is still running",
                        "body": {
                            "user_id": 1,
                            "manager_id": 1,
                            "start": "2026-03-02T08:00:00Z",
                            "end": null
                        }
                    }
                ],
                "x-plnnrly-permission": "absences::manage",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:absences"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/AbsenceList",
                    "detail": "#/components/schemas/Absence"
                }
            },
            "x-plnnrly-allow": [
                "GET",
                "POST"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/absences/{absence}": {
            "get": {
                "operationId": "api.v2.absences.show",
                "summary": "Read an absence",
                "description": "Returns one absence. The summary and detail views are identical on this resource: every field is small enough for a list row. `user` and `manager` render as `{\"id\": N}` references; `?include=user,manager` expands them, and that expansion additionally requires `users::read` and a token scope covering the users prefix. An absence whose employee has since been deleted keeps the reference form rather than expanding.",
                "tags": [
                    "Leave"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:absences"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:absences"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "absence",
                        "in": "path",
                        "description": "The Absence's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Absence"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "One absence with both users expanded",
                        "path": {
                            "absence": 1
                        },
                        "query": {
                            "include": "user,manager"
                        }
                    }
                ],
                "x-plnnrly-permission": "absences::manage",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:read:absences"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/AbsenceList",
                    "detail": "#/components/schemas/Absence"
                }
            },
            "patch": {
                "operationId": "api.v2.absences.update",
                "summary": "Update an absence",
                "description": "Partially updates an absence: absent keys are untouched, an empty body is rejected. Send `end` to close a running absence, or `end: null` to reopen one; send `manager_id: null` to detach the manager. `user_id` may be moved to another employee. `start` and `end` are stored exactly as sent, with no timezone conversion. Naming a `user_id` or `manager_id` additionally requires read access to users (`users::read`) and a token covering that prefix. Note: `end` is only checked against `start` when both are sent in the same request, so sending `end` alone can leave it before the stored `start`; an absence in that state counts as zero minutes everywhere it is read. Send `If-Match` with the `ETag` from your last read to detect a concurrent edit, which answers `412`. Treat it as best-effort only: this resource carries no concurrency token, and `updated_at` is second-precision, so two writes inside the same second are indistinguishable.",
                "tags": [
                    "Leave"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:absences"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:absences"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "absence",
                        "in": "path",
                        "description": "The Absence's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "user_id": {
                                        "type": "integer",
                                        "x-plnnrly-references": {
                                            "resource": "User",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "users",
                                        "description": "The employee this absence is for. Writing it requires the `absences::manage` permission."
                                    },
                                    "manager_id": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "x-plnnrly-references": {
                                            "resource": "User",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "users",
                                        "description": "Manager who filed or approved the absence, or `null` when none was recorded. The foreign key nulls on delete, so removing the manager leaves the absence behind unattributed."
                                    },
                                    "start": {
                                        "type": "string",
                                        "format": "date-time",
                                        "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$",
                                        "description": "When the absence begins. RFC 3339 UTC."
                                    },
                                    "end": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "format": "date-time",
                                        "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$",
                                        "x-plnnrly-constraint": "Must be on or after start.",
                                        "description": "When the absence ends, or `null` while it is still running. An ongoing absence reaches through now everywhere it is read (planning capacity and reporting both count it), and that reach is uncapped, so an absence nobody closed keeps consuming capacity. RFC 3339 UTC."
                                    }
                                },
                                "additionalProperties": false,
                                "minProperties": 1
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The updated record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Absence"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Close a running absence",
                        "path": {
                            "absence": 1
                        },
                        "body": {
                            "end": "2026-03-06T17:00:00Z"
                        },
                        "headers": {
                            "If-Match": "*"
                        }
                    }
                ],
                "x-plnnrly-permission": "absences::manage",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:absences"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/AbsenceList",
                    "detail": "#/components/schemas/Absence"
                }
            },
            "delete": {
                "operationId": "api.v2.absences.destroy",
                "summary": "Delete an absence",
                "description": "Permanently deletes an absence. This resource is hard-delete: there is no restore action, no `force` parameter and no `trashed` parameter. Nothing blocks the delete, and the capacity the absence was consuming in planning and reporting comes straight back. The deletion itself is recorded in the absence audit log.",
                "tags": [
                    "Leave"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:destroy:absences"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "absence",
                        "in": "path",
                        "description": "The Absence's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "responses": {
                    "204": {
                        "description": "Deleted. No body.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Delete an absence",
                        "path": {
                            "absence": 1
                        }
                    }
                ],
                "x-plnnrly-permission": "absences::manage",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:destroy:absences"
                ]
            },
            "x-plnnrly-allow": [
                "GET",
                "PATCH",
                "DELETE"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/budget-groups": {
            "get": {
                "operationId": "api.v2.budget-groups.index",
                "summary": "List budget groups",
                "description": "Returns the organisation's budget groups, sorted by name A→Z, each with the number of budgets filed under it. Pass `view=detail` for the full payload at three read units instead of one; the two views are identical on this resource.",
                "tags": [
                    "Projects"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:budget_groups"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:budget_groups"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/PerPage"
                    },
                    {
                        "$ref": "#/components/parameters/Cursor"
                    },
                    {
                        "$ref": "#/components/parameters/PageNumber"
                    },
                    {
                        "$ref": "#/components/parameters/ViewSummary"
                    },
                    {
                        "name": "filter[created_at]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][gte]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][lt]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][gte]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][lt]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/BudgetGroupSort"
                    },
                    {
                        "name": "q",
                        "in": "query",
                        "description": "Free-text search over `name`. At most one substring match per request: `q` together with a `contains` filter is 400 invalid-query-parameter.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "minLength": 1
                        }
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "A page of records.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record.",
                                "schema": {
                                    "type": "string",
                                    "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/BudgetGroupList"
                                            }
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/BudgetGroupsIndexMeta"
                                        },
                                        "links": {
                                            "$ref": "#/components/schemas/PageLinks"
                                        }
                                    },
                                    "required": [
                                        "data",
                                        "meta",
                                        "links"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Search by name",
                        "query": {
                            "q": "development",
                            "sort": "name"
                        }
                    }
                ],
                "x-plnnrly-permission": "budget_groups::read",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1,
                    "view_detail_total": 3,
                    "surcharges": {
                        "q": 2
                    }
                },
                "x-plnnrly-token-ability": [
                    "api:read:budget_groups"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/BudgetGroupList",
                    "detail": "#/components/schemas/BudgetGroup"
                }
            },
            "post": {
                "operationId": "api.v2.budget-groups.store",
                "summary": "Create a budget group",
                "description": "Creates a budget group and returns it with a `Location` header. Both the name and the icon are required; the icon is an icon name such as `heroicon-o-tag` and is not checked against the available icons, so an unknown name renders as a missing icon in plnnrly.",
                "tags": [
                    "Projects"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:budget_groups"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:budget_groups"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "description": "Budget group name as shown in the UI. This is the default sort key."
                                    },
                                    "icon": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "description": "Icon identifier rendered beside the group, e.g. `heroicon-o-rectangle-stack`."
                                    }
                                },
                                "required": [
                                    "name",
                                    "icon"
                                ],
                                "additionalProperties": false
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Created.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "Idempotency-Replayed": {
                                "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise.",
                                "schema": {
                                    "type": "boolean",
                                    "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise."
                                }
                            },
                            "Location": {
                                "description": "The absolute URL of the created or queued resource.",
                                "schema": {
                                    "type": "string",
                                    "description": "The absolute URL of the created or queued resource."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/BudgetGroup"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "$ref": "#/components/responses/Problem409Idempotency"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "A development budget group",
                        "body": {
                            "name": "Development",
                            "icon": "heroicon-o-code-bracket"
                        }
                    }
                ],
                "x-plnnrly-permission": "budget_groups::create",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:budget_groups"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/BudgetGroupList",
                    "detail": "#/components/schemas/BudgetGroup"
                }
            },
            "x-plnnrly-allow": [
                "GET",
                "POST"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/budget-groups/{budget_group}": {
            "get": {
                "operationId": "api.v2.budget-groups.show",
                "summary": "Read a budget group",
                "description": "Returns one budget group with the number of budgets filed under it and a link to that filtered budget collection.",
                "tags": [
                    "Projects"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:budget_groups"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:budget_groups"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "budget_group",
                        "in": "path",
                        "description": "The BudgetGroup's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/BudgetGroup"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "One budget group",
                        "path": {
                            "budget_group": 1
                        }
                    }
                ],
                "x-plnnrly-permission": "budget_groups::read",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:read:budget_groups"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/BudgetGroupList",
                    "detail": "#/components/schemas/BudgetGroup"
                }
            },
            "patch": {
                "operationId": "api.v2.budget-groups.update",
                "summary": "Update a budget group",
                "description": "Partially updates a budget group: absent keys are untouched and an empty body is rejected. Send `If-Match` with the `ETag` from your last read to detect a concurrent edit, which answers `412`.",
                "tags": [
                    "Projects"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:budget_groups"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:budget_groups"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "budget_group",
                        "in": "path",
                        "description": "The BudgetGroup's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "description": "Budget group name as shown in the UI. This is the default sort key."
                                    },
                                    "icon": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "description": "Icon identifier rendered beside the group, e.g. `heroicon-o-rectangle-stack`."
                                    }
                                },
                                "additionalProperties": false,
                                "minProperties": 1
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The updated record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/BudgetGroup"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Rename a budget group",
                        "path": {
                            "budget_group": 1
                        },
                        "body": {
                            "name": "Development and QA"
                        },
                        "headers": {
                            "If-Match": "*"
                        }
                    }
                ],
                "x-plnnrly-permission": "budget_groups::update",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:budget_groups"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/BudgetGroupList",
                    "detail": "#/components/schemas/BudgetGroup"
                }
            },
            "delete": {
                "operationId": "api.v2.budget-groups.destroy",
                "summary": "Delete a budget group",
                "description": "Deletes a budget group permanently: it uses no soft deletes, so there is no restore. Every budget in the group is silently ungrouped, and the organisation's Exact item id, VAT code, cost centre and local rate defaults for the group are deleted with it, which changes the output of subsequent project sales invoices. Team preferences referencing the group are left pointing at an id that no longer exists.",
                "tags": [
                    "Projects"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:destroy:budget_groups"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "budget_group",
                        "in": "path",
                        "description": "The BudgetGroup's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "responses": {
                    "204": {
                        "description": "Deleted. No body.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Delete a budget group",
                        "path": {
                            "budget_group": 1
                        }
                    }
                ],
                "x-plnnrly-permission": "budget_groups::delete",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:destroy:budget_groups"
                ]
            },
            "x-plnnrly-allow": [
                "GET",
                "PATCH",
                "DELETE"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/budgets": {
            "get": {
                "operationId": "api.v2.budgets.index",
                "summary": "List budgets",
                "description": "Returns the organisation's budgets, lowest position first, each with the number of kanban tasks the caller may see. There is no `projects/{project}/budgets` path: pass `filter[project_id]` to read one project's budgets, or `filter[budget_group_id]` to read one group's. Positions are numbered across the whole organisation and renumbered per project in plnnrly, so a page spanning several projects interleaves and ties break by id. Pass `view=detail` for the full payload at three read units instead of one; the two views are identical on this resource.",
                "tags": [
                    "Projects"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:budgets"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:budgets"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/PerPage"
                    },
                    {
                        "$ref": "#/components/parameters/Cursor"
                    },
                    {
                        "$ref": "#/components/parameters/BudgetPageNumber"
                    },
                    {
                        "$ref": "#/components/parameters/ViewSummary"
                    },
                    {
                        "name": "filter[budget_group_id]",
                        "in": "query",
                        "description": "Budget group the budget is filed under.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[budget_group_id][in]",
                        "in": "query",
                        "description": "Budget group the budget is filed under. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[budget_group_id][null]",
                        "in": "query",
                        "description": "Budget group the budget is filed under. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[created_at]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][gte]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][lt]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[end_date]",
                        "in": "query",
                        "description": "Last day the budget covers.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[end_date][gte]",
                        "in": "query",
                        "description": "Last day the budget covers.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[end_date][lte]",
                        "in": "query",
                        "description": "Last day the budget covers.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[end_date][null]",
                        "in": "query",
                        "description": "Last day the budget covers. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[is_billable]",
                        "in": "query",
                        "description": "Whether time logged on this budget is billable.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[is_open]",
                        "in": "query",
                        "description": "Whether the budget is open-ended, i.e. its spendable cap is ignored and the over-budget warning is skipped.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[project_id]",
                        "in": "query",
                        "description": "Owning project.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[project_id][in]",
                        "in": "query",
                        "description": "Owning project. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[start_date]",
                        "in": "query",
                        "description": "First day the budget covers.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[start_date][gte]",
                        "in": "query",
                        "description": "First day the budget covers.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[start_date][lte]",
                        "in": "query",
                        "description": "First day the budget covers.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[start_date][null]",
                        "in": "query",
                        "description": "First day the budget covers. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[status]",
                        "in": "query",
                        "description": "Lifecycle status of the budget.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "active",
                                "inactive",
                                "blocked"
                            ],
                            "x-extensible-enum": true
                        }
                    },
                    {
                        "name": "filter[status][ne]",
                        "in": "query",
                        "description": "Lifecycle status of the budget.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "active",
                                "inactive",
                                "blocked"
                            ],
                            "x-extensible-enum": true
                        }
                    },
                    {
                        "name": "filter[status][in]",
                        "in": "query",
                        "description": "Lifecycle status of the budget. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "string",
                                "enum": [
                                    "active",
                                    "inactive",
                                    "blocked"
                                ],
                                "x-extensible-enum": true
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[updated_at]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][gte]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][lt]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/BudgetSort"
                    },
                    {
                        "name": "q",
                        "in": "query",
                        "description": "Free-text search over `description`. At most one substring match per request: `q` together with a `contains` filter is 400 invalid-query-parameter.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "minLength": 1
                        }
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "A page of records.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record.",
                                "schema": {
                                    "type": "string",
                                    "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/BudgetList"
                                            }
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/BudgetsIndexMeta"
                                        },
                                        "links": {
                                            "$ref": "#/components/schemas/PageLinks"
                                        }
                                    },
                                    "required": [
                                        "data",
                                        "meta",
                                        "links"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "One project's open budgets",
                        "query": {
                            "filter[project_id]": 1,
                            "filter[is_open]": "true",
                            "sort": "order_column"
                        }
                    }
                ],
                "x-plnnrly-permission": "budgets::read",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1,
                    "view_detail_total": 3,
                    "surcharges": {
                        "q": 2
                    }
                },
                "x-plnnrly-token-ability": [
                    "api:read:budgets"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/BudgetList",
                    "detail": "#/components/schemas/Budget"
                }
            },
            "post": {
                "operationId": "api.v2.budgets.store",
                "summary": "Create a budget",
                "description": "Creates a budget on the project named in the body and returns it with a `Location` header. `project_id` is settable here and nowhere else: a budget cannot be moved to another project afterwards. budget and spendable are minutes, not money. The position is set by the server to the highest existing position in the organisation plus one. The write also bumps the owning project's `updated_at`, so the project's `ETag` changes and it reappears in an incremental `filter[updated_at][gte]` window, and it is recorded in the project's activity timeline rather than a timeline of its own.",
                "tags": [
                    "Projects"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:budgets"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:budgets"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "project_id": {
                                        "type": "integer",
                                        "x-plnnrly-references": {
                                            "resource": "Project",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "projects",
                                        "description": "Owning project. Settable at create time only; a budget cannot be moved to another project."
                                    },
                                    "description": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "description": "Budget description as shown in the UI. This is the budget's label and the only field `?q=` searches; a budget has no title."
                                    },
                                    "budget": {
                                        "type": "integer",
                                        "minimum": 0,
                                        "description": "Budgeted amount of work, in minutes."
                                    },
                                    "spendable": {
                                        "type": "integer",
                                        "minimum": 0,
                                        "description": "The portion of the budget that may be allocated to tasks, in minutes. A stored value, never decremented by logged time."
                                    },
                                    "status": {
                                        "type": "string",
                                        "enum": [
                                            "active",
                                            "inactive",
                                            "blocked"
                                        ],
                                        "x-enum-descriptions": {
                                            "active": "The default: work is booked against this budget.",
                                            "blocked": "Paused, usually because the project itself is on hold.",
                                            "inactive": "Retired, kept for history and no longer planned against."
                                        },
                                        "x-extensible-enum": true,
                                        "description": "Lifecycle status of the budget."
                                    },
                                    "is_open": {
                                        "type": "boolean",
                                        "description": "Whether the budget is open-ended: when `true` the spendable cap is ignored, plnnrly shows \"open\" in place of a minute total and the over-budget warning is skipped."
                                    },
                                    "is_billable": {
                                        "type": "boolean",
                                        "description": "Whether time logged on this budget is billable."
                                    },
                                    "start_date": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "format": "date",
                                        "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                                        "description": "First day the budget covers, `null` when it has no start."
                                    },
                                    "end_date": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "format": "date",
                                        "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                                        "x-plnnrly-constraint": "Must be on or after start_date.",
                                        "description": "Last day the budget covers, `null` when it has no end."
                                    },
                                    "budget_group_id": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "x-plnnrly-references": {
                                            "resource": "BudgetGroup",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "budget_groups",
                                        "description": "Budget group this budget is filed under, `null` when ungrouped."
                                    }
                                },
                                "required": [
                                    "project_id",
                                    "description",
                                    "budget"
                                ],
                                "additionalProperties": false
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Created.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "Idempotency-Replayed": {
                                "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise.",
                                "schema": {
                                    "type": "boolean",
                                    "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise."
                                }
                            },
                            "Location": {
                                "description": "The absolute URL of the created or queued resource.",
                                "schema": {
                                    "type": "string",
                                    "description": "The absolute URL of the created or queued resource."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Budget"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "$ref": "#/components/responses/Problem409Idempotency"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "A capped, billable budget",
                        "body": {
                            "project_id": 1,
                            "description": "Front-end development",
                            "budget": 4800,
                            "spendable": 4200,
                            "is_open": false,
                            "is_billable": true
                        }
                    }
                ],
                "x-plnnrly-permission": "budgets::create",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:budgets"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/BudgetList",
                    "detail": "#/components/schemas/Budget"
                }
            },
            "x-plnnrly-allow": [
                "GET",
                "POST"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/budgets/{budget}": {
            "get": {
                "operationId": "api.v2.budgets.show",
                "summary": "Read a budget",
                "description": "Returns one budget with its project, its budget group and the number of kanban tasks the caller may see, plus a link to that filtered task collection. budget and spendable are minutes, not money.",
                "tags": [
                    "Projects"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:budgets"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:budgets"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "budget",
                        "in": "path",
                        "description": "The Budget's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Budget"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "One budget",
                        "path": {
                            "budget": 1
                        }
                    }
                ],
                "x-plnnrly-permission": "budgets::read",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:read:budgets"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/BudgetList",
                    "detail": "#/components/schemas/Budget"
                }
            },
            "patch": {
                "operationId": "api.v2.budgets.update",
                "summary": "Update a budget",
                "description": "Partially updates a budget: absent keys are untouched and an empty body is rejected. `project_id` is not accepted, so a budget cannot be moved to another project, and the position is not writable: there is no reorder endpoint. `start_date`, `end_date` and `budget_group_id` accept `null` to clear them. The write bumps the owning project's `updated_at`, so the project's `ETag` changes and it reappears in an incremental `filter[updated_at][gte]` window, and it lands in the project's activity timeline rather than a timeline of its own. Send `If-Match` with the `ETag` from your last read to detect a concurrent edit, which answers `412`.",
                "tags": [
                    "Projects"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:budgets"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:budgets"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "budget",
                        "in": "path",
                        "description": "The Budget's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "description": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "description": "Budget description as shown in the UI. This is the budget's label and the only field `?q=` searches; a budget has no title."
                                    },
                                    "budget": {
                                        "type": "integer",
                                        "minimum": 0,
                                        "description": "Budgeted amount of work, in minutes."
                                    },
                                    "spendable": {
                                        "type": "integer",
                                        "minimum": 0,
                                        "description": "The portion of the budget that may be allocated to tasks, in minutes. A stored value, never decremented by logged time."
                                    },
                                    "status": {
                                        "type": "string",
                                        "enum": [
                                            "active",
                                            "inactive",
                                            "blocked"
                                        ],
                                        "x-enum-descriptions": {
                                            "active": "The default: work is booked against this budget.",
                                            "blocked": "Paused, usually because the project itself is on hold.",
                                            "inactive": "Retired, kept for history and no longer planned against."
                                        },
                                        "x-extensible-enum": true,
                                        "description": "Lifecycle status of the budget."
                                    },
                                    "is_open": {
                                        "type": "boolean",
                                        "description": "Whether the budget is open-ended: when `true` the spendable cap is ignored, plnnrly shows \"open\" in place of a minute total and the over-budget warning is skipped."
                                    },
                                    "is_billable": {
                                        "type": "boolean",
                                        "description": "Whether time logged on this budget is billable."
                                    },
                                    "start_date": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "format": "date",
                                        "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                                        "description": "First day the budget covers, `null` when it has no start."
                                    },
                                    "end_date": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "format": "date",
                                        "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                                        "x-plnnrly-constraint": "Must be on or after start_date.",
                                        "description": "Last day the budget covers, `null` when it has no end."
                                    },
                                    "budget_group_id": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "x-plnnrly-references": {
                                            "resource": "BudgetGroup",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "budget_groups",
                                        "description": "Budget group this budget is filed under, `null` when ungrouped."
                                    }
                                },
                                "additionalProperties": false,
                                "minProperties": 1
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The updated record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Budget"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Cap a budget and raise its allowance",
                        "path": {
                            "budget": 1
                        },
                        "body": {
                            "budget": 6000,
                            "is_open": false
                        },
                        "headers": {
                            "If-Match": "*"
                        }
                    }
                ],
                "x-plnnrly-permission": "budgets::update",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:budgets"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/BudgetList",
                    "detail": "#/components/schemas/Budget"
                }
            },
            "delete": {
                "operationId": "api.v2.budgets.destroy",
                "summary": "Delete a budget",
                "description": "Deletes a budget permanently: it uses no soft deletes, so there is no restore and no force parameter. The delete reaches further: every kanban task on the budget and every legacy invoice row referencing it are deleted with it, and none of those deletions is recorded in an activity log. Sales-invoice rows survive and are detached, their budget reference set to `null`. A budget with logged time answers `409` with `conflict.reason` `has_logged_time`: move or delete the worklogs first. The delete bumps the owning project's `updated_at` and is recorded in the project's activity timeline rather than a timeline of its own.",
                "tags": [
                    "Projects"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:destroy:budgets"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "budget",
                        "in": "path",
                        "description": "The Budget's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "responses": {
                    "204": {
                        "description": "Deleted. No body.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "description": "Conflict.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/StateConflictProblem"
                                },
                                "examples": {
                                    "state-conflict": {
                                        "$ref": "#/components/examples/StateConflictProblem"
                                    }
                                }
                            }
                        }
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Delete a budget",
                        "path": {
                            "budget": 1
                        }
                    }
                ],
                "x-plnnrly-permission": "budgets::delete",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:destroy:budgets"
                ]
            },
            "x-plnnrly-allow": [
                "GET",
                "PATCH",
                "DELETE"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/checkins": {
            "get": {
                "operationId": "api.v2.checkins.index",
                "summary": "List checkins",
                "description": "Returns attendance records, one per clock-in, newest first. Every operation on this resource requires `checkins::read`, which is the attendance report permission in plnnrly, and a holder sees the whole team; there is no own-rows view for a caller without it. `filter[checked_out_at][null]=true` returns the still-open checkins, which is the only way to ask who is currently clocked in. There is no `week` parameter: filter a week as `filter[checked_in_at][gte]=2026-08-31T00:00:00Z&filter[checked_in_at][lte]=2026-09-06T23:59:59Z`. `meta.page.total` is `null` unless you pass `with_total=1`. `?include=user` expands the employee reference and additionally requires the `users::read` permission and a token scope covering the users prefix; without either it is a `400`, never a `403`.",
                "tags": [
                    "Leave"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:checkins"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:checkins"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/PerPage"
                    },
                    {
                        "$ref": "#/components/parameters/Cursor"
                    },
                    {
                        "$ref": "#/components/parameters/ViewSummary"
                    },
                    {
                        "$ref": "#/components/parameters/WithTotal"
                    },
                    {
                        "name": "filter[checked_in_at]",
                        "in": "query",
                        "description": "When the employee clocked in.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[checked_in_at][gte]",
                        "in": "query",
                        "description": "When the employee clocked in.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[checked_in_at][lte]",
                        "in": "query",
                        "description": "When the employee clocked in.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[checked_out_at]",
                        "in": "query",
                        "description": "When the employee clocked out. `null` selects the still-open checkins.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[checked_out_at][gte]",
                        "in": "query",
                        "description": "When the employee clocked out. `null` selects the still-open checkins.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[checked_out_at][lte]",
                        "in": "query",
                        "description": "When the employee clocked out. `null` selects the still-open checkins.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[checked_out_at][null]",
                        "in": "query",
                        "description": "When the employee clocked out. `null` selects the still-open checkins. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[created_at]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][gte]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][lt]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][gte]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][lt]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[user_id]",
                        "in": "query",
                        "description": "Whose checkins to return. Accepts the literal `me`. Naming another employee requires the `checkins::read` permission.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[user_id][in]",
                        "in": "query",
                        "description": "Whose checkins to return. Accepts the literal `me`. Naming another employee requires the `checkins::read` permission. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "$ref": "#/components/parameters/CheckinSort"
                    },
                    {
                        "$ref": "#/components/parameters/CheckinInclude"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "A page of records.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record.",
                                "schema": {
                                    "type": "string",
                                    "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/CheckinList"
                                            }
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/CheckinsIndexMeta"
                                        },
                                        "links": {
                                            "$ref": "#/components/schemas/PageLinks"
                                        }
                                    },
                                    "required": [
                                        "data",
                                        "meta",
                                        "links"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Who is currently clocked in",
                        "query": {
                            "filter[checked_out_at][null]": "true",
                            "include": "user"
                        }
                    },
                    {
                        "name": "One employee's week, oldest first",
                        "query": {
                            "filter[user_id]": "me",
                            "filter[checked_in_at][gte]": "2026-08-31T00:00:00Z",
                            "filter[checked_in_at][lte]": "2026-09-06T23:59:59Z",
                            "sort": "checked_in_at"
                        }
                    }
                ],
                "x-plnnrly-permission": "checkins::read",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1,
                    "view_detail_total": 3,
                    "surcharges": {
                        "with_total": 2,
                        "include": 1
                    }
                },
                "x-plnnrly-token-ability": [
                    "api:read:checkins"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/CheckinList",
                    "detail": "#/components/schemas/Checkin"
                }
            },
            "post": {
                "operationId": "api.v2.checkins.store",
                "summary": "Check in",
                "description": "Opens a checkin for the authenticated user and returns it with a `Location` header. `user_id` is not accepted: a checkin always belongs to the caller, and sending the key is a `422`; there is no way to check another employee in, just as there is none in plnnrly. Omit `checked_in_at` to clock in now. Send `checked_out_at` as well to record an already-finished period in one call, which is what a backfill client does. Nothing rejects a second still-open checkin: plnnrly hides its check-in button while one is open, but this endpoint answers `201` either way, so check `filter[checked_out_at][null]=true` first if you need that guarantee. This operation declares no permission of its own, but checking in still requires `checkins::read`, so unlike the check-in button in plnnrly, this endpoint is not open to every employee.",
                "tags": [
                    "Leave"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:checkins"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:checkins"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "checked_in_at": {
                                        "type": "string",
                                        "format": "date-time",
                                        "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$",
                                        "description": "When the employee clocked in."
                                    },
                                    "checked_out_at": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "format": "date-time",
                                        "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$",
                                        "x-plnnrly-constraint": "Must be on or after checked_in_at.",
                                        "description": "When the employee clocked out, or `null` while the checkin is still open. A `null` value marks the checkin plnnrly shows as active."
                                    }
                                },
                                "additionalProperties": false,
                                "minProperties": 1
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Created.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "Idempotency-Replayed": {
                                "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise.",
                                "schema": {
                                    "type": "boolean",
                                    "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise."
                                }
                            },
                            "Location": {
                                "description": "The absolute URL of the created or queued resource.",
                                "schema": {
                                    "type": "string",
                                    "description": "The absolute URL of the created or queued resource."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Checkin"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "$ref": "#/components/responses/Problem409Idempotency"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Clock in now",
                        "body": {}
                    },
                    {
                        "name": "Backfill a finished shift",
                        "body": {
                            "checked_in_at": "2026-08-31T08:02:00Z",
                            "checked_out_at": "2026-08-31T16:48:00Z"
                        }
                    }
                ],
                "x-plnnrly-permission": null,
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:checkins"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/CheckinList",
                    "detail": "#/components/schemas/Checkin"
                }
            },
            "x-plnnrly-allow": [
                "GET",
                "POST"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/checkins/{checkin}": {
            "get": {
                "operationId": "api.v2.checkins.show",
                "summary": "Read a checkin",
                "description": "Returns one checkin. The summary and detail views are identical on this resource: every field is small enough for a list row. A checkin belonging to another employee is `404` unless the caller holds `checkins::read`; the row is invisible, so its existence stays invisible. `user` renders as an `{\"id\": N}` reference; `?include=user` expands it, and that expansion additionally requires `users::read` and a token scope covering the users prefix.",
                "tags": [
                    "Leave"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:checkins"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:checkins"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "checkin",
                        "in": "path",
                        "description": "The Checkin's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Checkin"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "One checkin with its employee expanded",
                        "path": {
                            "checkin": 1
                        },
                        "query": {
                            "include": "user"
                        }
                    }
                ],
                "x-plnnrly-permission": "checkins::read",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:read:checkins"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/CheckinList",
                    "detail": "#/components/schemas/Checkin"
                }
            },
            "patch": {
                "operationId": "api.v2.checkins.update",
                "summary": "Check out, or correct a checkin",
                "description": "Partially updates a checkin: absent keys are untouched, an empty body is rejected. Sending `checked_out_at` is the checkout: there is no `actions/check-out` route, because the write only ever fails on an invalid value. Send `checked_out_at: null` to re-open a closed checkin. `user_id` is not accepted, so a checkin cannot be moved to another employee. Send `If-Match` with the `ETag` from your last read to detect a concurrent edit, which answers `412`. Treat it as best-effort only: this resource carries no concurrency token, and `updated_at` is second-precision, so two writes inside the same second are indistinguishable. This operation declares no permission of its own, but it still requires `checkins::read`; a caller without it gets `404` on every checkin, their own included.",
                "tags": [
                    "Leave"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:checkins"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:checkins"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "checkin",
                        "in": "path",
                        "description": "The Checkin's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "checked_in_at": {
                                        "type": "string",
                                        "format": "date-time",
                                        "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$",
                                        "description": "When the employee clocked in."
                                    },
                                    "checked_out_at": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "format": "date-time",
                                        "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$",
                                        "x-plnnrly-constraint": "Must be on or after checked_in_at.",
                                        "description": "When the employee clocked out, or `null` while the checkin is still open. A `null` value marks the checkin plnnrly shows as active."
                                    }
                                },
                                "additionalProperties": false,
                                "minProperties": 1
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The updated record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Checkin"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Clock out",
                        "path": {
                            "checkin": 1
                        },
                        "body": {
                            "checked_out_at": "2026-08-31T16:48:00Z"
                        },
                        "headers": {
                            "If-Match": "*"
                        }
                    },
                    {
                        "name": "Re-open a checkin closed by mistake",
                        "path": {
                            "checkin": 1
                        },
                        "body": {
                            "checked_out_at": null
                        },
                        "headers": {
                            "If-Match": "*"
                        }
                    }
                ],
                "x-plnnrly-permission": null,
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:checkins"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/CheckinList",
                    "detail": "#/components/schemas/Checkin"
                }
            },
            "delete": {
                "operationId": "api.v2.checkins.destroy",
                "summary": "Delete a checkin",
                "description": "Permanently deletes a checkin. This resource is hard-delete: there is no restore action, no `force` parameter and no `trashed` parameter. Nothing blocks the delete: a checkin carries no dependants, and removing one simply takes the period out of the attendance report. This operation declares no permission of its own, but it still requires `checkins::read`; a caller without it gets `404` on every checkin.",
                "tags": [
                    "Leave"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:destroy:checkins"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "checkin",
                        "in": "path",
                        "description": "The Checkin's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "responses": {
                    "204": {
                        "description": "Deleted. No body.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Delete a checkin",
                        "path": {
                            "checkin": 1
                        }
                    }
                ],
                "x-plnnrly-permission": null,
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:destroy:checkins"
                ]
            },
            "x-plnnrly-allow": [
                "GET",
                "PATCH",
                "DELETE"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/comments/{comment}": {
            "get": {
                "operationId": "api.v2.comments.show",
                "summary": "Read a comment",
                "description": "Returns one comment in the detail view, reachable by id from either parent. The `commentable_type` / `commentable_id` pair names the record it hangs off; the commentable itself is a reference and is not expandable. Reading a comment requires being able to read its parent, so the permission depends on what the comment hangs off: `project_management::view_backlog` (checked as `project_management::manage_work_items`) for a comment on a work item, and the drive prefix's access permission (`project_drive::access`, `customer_drive::access`, `organization_drive::access` or `user_drive::access`, whichever the document sits under) for a comment on a drive item. The token must cover that same prefix: a token without it gets `404`, not `403`. Comments are hard-deleted, so a deleted one is simply 404 and `trashed` is not an accepted parameter.",
                "tags": [
                    "Work items"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:project_management"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:read:project_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:read:customer_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:read:organization_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:read:user_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:project_management"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:project_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:customer_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:organization_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:user_drive"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "comment",
                        "in": "path",
                        "description": "The Comment's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Comment"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "One comment with its author expanded",
                        "path": {
                            "comment": 1
                        },
                        "query": {
                            "include": "author"
                        }
                    }
                ],
                "x-plnnrly-permission": "project_management::view_backlog",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:read:project_management",
                    "api:read:project_drive",
                    "api:read:customer_drive",
                    "api:read:organization_drive",
                    "api:read:user_drive"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/CommentList",
                    "detail": "#/components/schemas/Comment"
                }
            },
            "patch": {
                "operationId": "api.v2.comments.update",
                "summary": "Edit a comment",
                "description": "Partially updates one comment: `body` is the only writable key and absent keys are untouched. This mirrors the edit action on a comment thread in plnnrly and follows the same rules: Editing is author-only. No permission lets a manager edit somebody else's comment, so a PATCH by a non-author is `403` `forbidden-by-policy` and there is no override to ask for. On top of authorship the caller must still be able to comment on the parent, which is `project_management::manage_work_items` for a work item and read access to the drive document for a drive item; the token must cover that parent's prefix. Re-parenting is not a field write and is not available: sending `commentable_type` or `commentable_id` is a `422`. Mention spans present in the body you read may be sent back verbatim and are preserved; note that the response body is sanitised, so it can differ from the exact bytes you sent. Send `If-Match` with the `ETag` from your last read to detect a concurrent edit, which answers `412`; a comment has no `version` field, so its `ETag` is derived from `updated_at`, and the `ETag` in the response is the one to send next.",
                "tags": [
                    "Work items"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:project_management"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:write:project_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:write:customer_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:write:organization_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:write:user_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:project_management"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:project_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:customer_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:organization_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:user_drive"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "comment",
                        "in": "path",
                        "description": "The Comment's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "body": {
                                        "type": "string",
                                        "maxLength": 65535,
                                        "description": "Comment body as rich text. HTML from the plnnrly editor, sanitised on the way out, so what you read back can differ from the bytes you sent. Mention spans (`<span data-type=\"mention\" data-id=\"3\">`) survive untouched and may be posted back verbatim; constructing a new mention is not supported in v2.0."
                                    }
                                },
                                "additionalProperties": false,
                                "minProperties": 1
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The updated record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Comment"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "description": "Forbidden.\n\n`forbidden-by-policy`: You did not write this comment. Only its author can edit a comment, and no permission lets anyone else do it. You also get this answer when you can see the work item or drive item the comment belongs to but may not comment on it.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "oneOf": [
                                        {
                                            "$ref": "#/components/schemas/ForbiddenByPolicyProblem"
                                        },
                                        {
                                            "$ref": "#/components/schemas/InsufficientPermissionProblem"
                                        },
                                        {
                                            "$ref": "#/components/schemas/InsufficientTokenScopeProblem"
                                        }
                                    ],
                                    "discriminator": {
                                        "propertyName": "type",
                                        "mapping": {
                                            "https://developers.plnnrly.com/api/v2/problems/forbidden-by-policy": "#/components/schemas/ForbiddenByPolicyProblem",
                                            "https://developers.plnnrly.com/api/v2/problems/insufficient-permission": "#/components/schemas/InsufficientPermissionProblem",
                                            "https://developers.plnnrly.com/api/v2/problems/insufficient-token-scope": "#/components/schemas/InsufficientTokenScopeProblem"
                                        }
                                    }
                                },
                                "examples": {
                                    "forbidden-by-policy": {
                                        "$ref": "#/components/examples/ForbiddenByPolicyProblem"
                                    },
                                    "insufficient-permission": {
                                        "$ref": "#/components/examples/InsufficientPermissionProblem"
                                    },
                                    "insufficient-token-scope": {
                                        "$ref": "#/components/examples/InsufficientTokenScopeProblem"
                                    }
                                }
                            }
                        }
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Fix a typo in your own comment",
                        "path": {
                            "comment": 1
                        },
                        "body": {
                            "body": "<p>Deployed to staging, ready for review.</p>"
                        },
                        "headers": {
                            "If-Match": "*"
                        }
                    }
                ],
                "x-plnnrly-permission": "project_management::manage_work_items",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:project_management",
                    "api:write:project_drive",
                    "api:write:customer_drive",
                    "api:write:organization_drive",
                    "api:write:user_drive"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/CommentList",
                    "detail": "#/components/schemas/Comment"
                }
            },
            "delete": {
                "operationId": "api.v2.comments.destroy",
                "summary": "Delete a comment",
                "description": "Permanently deletes one comment and answers `204`. Deleting is author-only, on the same terms as editing: no permission lets a manager delete somebody else's comment, a DELETE by a non-author is `403` `forbidden-by-policy`, and the caller must additionally be able to comment on the parent (`project_management::manage_work_items` for a work item, read access to the drive document for a drive item), with a token covering that parent's prefix. There is no soft delete, no ?force parameter and no restore verb: a comment comes back only by being posted again, as a new comment with a new id and a new timestamp. A second DELETE of the same id is `404`.",
                "tags": [
                    "Work items"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:destroy:project_management"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:destroy:project_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:destroy:customer_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:destroy:organization_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:destroy:user_drive"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "comment",
                        "in": "path",
                        "description": "The Comment's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "responses": {
                    "204": {
                        "description": "Deleted. No body.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "description": "Forbidden.\n\n`forbidden-by-policy`: You did not write this comment. Only its author can delete a comment, and no permission lets anyone else do it. You also get this answer when you can see the work item or drive item the comment belongs to but may not comment on it.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "oneOf": [
                                        {
                                            "$ref": "#/components/schemas/ForbiddenByPolicyProblem"
                                        },
                                        {
                                            "$ref": "#/components/schemas/InsufficientPermissionProblem"
                                        },
                                        {
                                            "$ref": "#/components/schemas/InsufficientTokenScopeProblem"
                                        }
                                    ],
                                    "discriminator": {
                                        "propertyName": "type",
                                        "mapping": {
                                            "https://developers.plnnrly.com/api/v2/problems/forbidden-by-policy": "#/components/schemas/ForbiddenByPolicyProblem",
                                            "https://developers.plnnrly.com/api/v2/problems/insufficient-permission": "#/components/schemas/InsufficientPermissionProblem",
                                            "https://developers.plnnrly.com/api/v2/problems/insufficient-token-scope": "#/components/schemas/InsufficientTokenScopeProblem"
                                        }
                                    }
                                },
                                "examples": {
                                    "forbidden-by-policy": {
                                        "$ref": "#/components/examples/ForbiddenByPolicyProblem"
                                    },
                                    "insufficient-permission": {
                                        "$ref": "#/components/examples/InsufficientPermissionProblem"
                                    },
                                    "insufficient-token-scope": {
                                        "$ref": "#/components/examples/InsufficientTokenScopeProblem"
                                    }
                                }
                            }
                        }
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Remove your own comment",
                        "path": {
                            "comment": 1
                        }
                    }
                ],
                "x-plnnrly-permission": "project_management::manage_work_items",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:destroy:project_management",
                    "api:destroy:project_drive",
                    "api:destroy:customer_drive",
                    "api:destroy:organization_drive",
                    "api:destroy:user_drive"
                ]
            },
            "x-plnnrly-allow": [
                "GET",
                "PATCH",
                "DELETE"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/company-branches": {
            "get": {
                "operationId": "api.v2.company-branches.index",
                "summary": "List company branches",
                "description": "Returns the organisation's company branches, sorted by name A to Z. Each row carries the number of customers assigned to the branch and a link to that filtered customer collection. Search with `q=` over the branch name.",
                "tags": [
                    "Organization"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:company_branches"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:company_branches"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/PerPage"
                    },
                    {
                        "$ref": "#/components/parameters/Cursor"
                    },
                    {
                        "$ref": "#/components/parameters/PageNumber"
                    },
                    {
                        "$ref": "#/components/parameters/ViewSummary"
                    },
                    {
                        "name": "filter[created_at]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][gte]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][lt]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][gte]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][lt]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/CompanyBranchSort"
                    },
                    {
                        "name": "q",
                        "in": "query",
                        "description": "Free-text search over `name`. At most one substring match per request: `q` together with a `contains` filter is 400 invalid-query-parameter.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "minLength": 1
                        }
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "A page of records.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record.",
                                "schema": {
                                    "type": "string",
                                    "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/CompanyBranchList"
                                            }
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/CompanyBranchesIndexMeta"
                                        },
                                        "links": {
                                            "$ref": "#/components/schemas/PageLinks"
                                        }
                                    },
                                    "required": [
                                        "data",
                                        "meta",
                                        "links"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, max-age=60",
                "x-plnnrly-examples": [
                    {
                        "name": "Find the Rotterdam branch",
                        "query": {
                            "q": "Rotterdam",
                            "sort": "name"
                        }
                    }
                ],
                "x-plnnrly-permission": "company_branches::read",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1,
                    "view_detail_total": 3,
                    "surcharges": {
                        "q": 2
                    }
                },
                "x-plnnrly-token-ability": [
                    "api:read:company_branches"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/CompanyBranchList",
                    "detail": "#/components/schemas/CompanyBranch"
                }
            },
            "post": {
                "operationId": "api.v2.company-branches.store",
                "summary": "Create a company branch",
                "description": "Creates a branch and returns it in the detail view with a `Location` header. Names are not required to be unique, as in plnnrly.",
                "tags": [
                    "Organization"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:company_branches"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:company_branches"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "description": "Branch name as shown in the UI, e.g. Rotterdam."
                                    }
                                },
                                "required": [
                                    "name"
                                ],
                                "additionalProperties": false
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Created.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "Idempotency-Replayed": {
                                "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise.",
                                "schema": {
                                    "type": "boolean",
                                    "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise."
                                }
                            },
                            "Location": {
                                "description": "The absolute URL of the created or queued resource.",
                                "schema": {
                                    "type": "string",
                                    "description": "The absolute URL of the created or queued resource."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/CompanyBranch"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "$ref": "#/components/responses/Problem409Idempotency"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "A second office",
                        "body": {
                            "name": "Rotterdam"
                        }
                    }
                ],
                "x-plnnrly-permission": "company_branches::create",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:company_branches"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/CompanyBranchList",
                    "detail": "#/components/schemas/CompanyBranch"
                }
            },
            "x-plnnrly-allow": [
                "GET",
                "POST"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/company-branches/{company_branch}": {
            "get": {
                "operationId": "api.v2.company-branches.show",
                "summary": "Read a company branch",
                "description": "Returns one branch in the detail view. Branches are hard-deleted, so a deleted branch is gone and answers `404`.",
                "tags": [
                    "Organization"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:company_branches"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:company_branches"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "company_branch",
                        "in": "path",
                        "description": "The CompanyBranch's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/CompanyBranch"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, max-age=60",
                "x-plnnrly-examples": [
                    {
                        "name": "One branch",
                        "path": {
                            "company_branch": 1
                        }
                    }
                ],
                "x-plnnrly-permission": "company_branches::read",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:read:company_branches"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/CompanyBranchList",
                    "detail": "#/components/schemas/CompanyBranch"
                }
            },
            "patch": {
                "operationId": "api.v2.company-branches.update",
                "summary": "Update a company branch",
                "description": "Partially updates a branch: absent keys are untouched. Send `If-Match` with the `ETag` from your last read to detect a concurrent edit, which answers `412`.",
                "tags": [
                    "Organization"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:company_branches"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:company_branches"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "company_branch",
                        "in": "path",
                        "description": "The CompanyBranch's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "description": "Branch name as shown in the UI, e.g. Rotterdam."
                                    }
                                },
                                "additionalProperties": false,
                                "minProperties": 1
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The updated record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/CompanyBranch"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Rename a branch",
                        "path": {
                            "company_branch": 1
                        },
                        "body": {
                            "name": "Rotterdam Zuid"
                        },
                        "headers": {
                            "If-Match": "*"
                        }
                    }
                ],
                "x-plnnrly-permission": "company_branches::update",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:company_branches"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/CompanyBranchList",
                    "detail": "#/components/schemas/CompanyBranch"
                }
            },
            "delete": {
                "operationId": "api.v2.company-branches.destroy",
                "summary": "Delete a company branch",
                "description": "Permanently deletes a branch: branches are not soft-deleted and there is no restore. Customers assigned to the branch are un-assigned rather than blocked, so a branch that is in use deletes successfully.",
                "tags": [
                    "Organization"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:destroy:company_branches"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "company_branch",
                        "in": "path",
                        "description": "The CompanyBranch's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "responses": {
                    "204": {
                        "description": "Deleted. No body.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Delete a branch",
                        "path": {
                            "company_branch": 1
                        }
                    }
                ],
                "x-plnnrly-permission": "company_branches::delete",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:destroy:company_branches"
                ]
            },
            "x-plnnrly-allow": [
                "GET",
                "PATCH",
                "DELETE"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/contact-roles": {
            "get": {
                "operationId": "api.v2.contact-roles.index",
                "summary": "List contact roles",
                "description": "Returns the contact roles the caller may read, by name A-Z. Roles are one list for the whole organisation, so the whole list is one short page. Nothing is includable on this resource: pass q to search the name, and read `contacts_count` or follow `links.contacts` for the contacts assigned a role.",
                "tags": [
                    "Customers"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:customers"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:customers"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/PerPage"
                    },
                    {
                        "$ref": "#/components/parameters/Cursor"
                    },
                    {
                        "$ref": "#/components/parameters/PageNumber"
                    },
                    {
                        "$ref": "#/components/parameters/ViewSummary"
                    },
                    {
                        "name": "filter[created_at]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][gte]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][lt]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][gte]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][lt]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ContactRoleSort"
                    },
                    {
                        "name": "q",
                        "in": "query",
                        "description": "Free-text search over `name`. At most one substring match per request: `q` together with a `contains` filter is 400 invalid-query-parameter.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "minLength": 1
                        }
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "A page of records.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record.",
                                "schema": {
                                    "type": "string",
                                    "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/ContactRoleList"
                                            }
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/ContactRolesIndexMeta"
                                        },
                                        "links": {
                                            "$ref": "#/components/schemas/PageLinks"
                                        }
                                    },
                                    "required": [
                                        "data",
                                        "meta",
                                        "links"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, max-age=60",
                "x-plnnrly-examples": [
                    {
                        "name": "Search the invoicing role",
                        "query": {
                            "q": "invoic"
                        }
                    }
                ],
                "x-plnnrly-permission": "customers::read",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1,
                    "view_detail_total": 3,
                    "surcharges": {
                        "q": 2
                    }
                },
                "x-plnnrly-token-ability": [
                    "api:read:customers"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/ContactRoleList",
                    "detail": "#/components/schemas/ContactRole"
                }
            },
            "post": {
                "operationId": "api.v2.contact-roles.store",
                "summary": "Create a contact role",
                "description": "Creates a contact role and returns it with a `Location` header. The name must be unique across the organisation, as in plnnrly.",
                "tags": [
                    "Customers"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:customers"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:customers"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "x-plnnrly-constraint": "Must be unique across ContactRole.",
                                        "x-plnnrly-unique": {
                                            "resource": "ContactRole",
                                            "field": "name"
                                        },
                                        "description": "Role name as shown in the UI, unique across the organisation. This is the default sort key and the field `?q=` searches."
                                    }
                                },
                                "required": [
                                    "name"
                                ],
                                "additionalProperties": false
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Created.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "Idempotency-Replayed": {
                                "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise.",
                                "schema": {
                                    "type": "boolean",
                                    "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise."
                                }
                            },
                            "Location": {
                                "description": "The absolute URL of the created or queued resource.",
                                "schema": {
                                    "type": "string",
                                    "description": "The absolute URL of the created or queued resource."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/ContactRole"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "$ref": "#/components/responses/Problem409Idempotency"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "An invoicing role",
                        "body": {
                            "name": "Invoicing"
                        }
                    }
                ],
                "x-plnnrly-permission": "customers::create",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:customers"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/ContactRoleList",
                    "detail": "#/components/schemas/ContactRole"
                }
            },
            "x-plnnrly-allow": [
                "GET",
                "POST"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/contact-roles/{contact_role}": {
            "get": {
                "operationId": "api.v2.contact-roles.show",
                "summary": "Read a contact role",
                "description": "Returns one contact role. Every field is in both views, so `view=detail` returns the identical shape. The response carries a strong `ETag`; send it back as `If-None-Match` to get a `304`.",
                "tags": [
                    "Customers"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:customers"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:customers"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "contact_role",
                        "in": "path",
                        "description": "The ContactRole's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/ContactRole"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, max-age=60",
                "x-plnnrly-examples": [
                    {
                        "name": "One contact role",
                        "path": {
                            "contact_role": 1
                        }
                    }
                ],
                "x-plnnrly-permission": "customers::read",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:read:customers"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/ContactRoleList",
                    "detail": "#/components/schemas/ContactRole"
                }
            },
            "patch": {
                "operationId": "api.v2.contact-roles.update",
                "summary": "Update a contact role",
                "description": "Partially updates a contact role: absent keys are untouched, and name is the only writable key. Renaming a role changes it for every contact assigned it. Send `If-Match` with the `ETag` from your last read to detect a concurrent edit, which answers `412`.",
                "tags": [
                    "Customers"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:customers"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:customers"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "contact_role",
                        "in": "path",
                        "description": "The ContactRole's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "x-plnnrly-constraint": "Must be unique across ContactRole.",
                                        "x-plnnrly-unique": {
                                            "resource": "ContactRole",
                                            "field": "name"
                                        },
                                        "description": "Role name as shown in the UI, unique across the organisation. This is the default sort key and the field `?q=` searches."
                                    }
                                },
                                "additionalProperties": false,
                                "minProperties": 1
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The updated record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/ContactRole"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Rename a role",
                        "path": {
                            "contact_role": 1
                        },
                        "body": {
                            "name": "Invoicing contact"
                        },
                        "headers": {
                            "If-Match": "*"
                        }
                    }
                ],
                "x-plnnrly-permission": "customers::update",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:customers"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/ContactRoleList",
                    "detail": "#/components/schemas/ContactRole"
                }
            },
            "delete": {
                "operationId": "api.v2.contact-roles.destroy",
                "summary": "Delete a contact role",
                "description": "Permanently deletes a contact role and returns `204`. Every contact holding the role is unassigned rather than the delete being refused, exactly as in plnnrly. Roles are not soft-deleted, so this cannot be reversed and there is no force parameter.",
                "tags": [
                    "Customers"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:destroy:customers"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "contact_role",
                        "in": "path",
                        "description": "The ContactRole's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "responses": {
                    "204": {
                        "description": "Deleted. No body.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Delete a role",
                        "path": {
                            "contact_role": 1
                        }
                    }
                ],
                "x-plnnrly-permission": "customers::delete",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:destroy:customers"
                ]
            },
            "x-plnnrly-allow": [
                "GET",
                "PATCH",
                "DELETE"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/contacts": {
            "get": {
                "operationId": "api.v2.contacts.index",
                "summary": "List contacts",
                "description": "Returns the contacts the caller may read, by name A-Z. Pass `filter[contactable_type]` and `filter[contactable_id]` together to list one customer's or one user's contacts (there is no nested path for them), or `filter[contact_role_id]` to list one role's. Pass q to search name and email, and `include=contactable,role` to expand the parent and the role.",
                "tags": [
                    "Customers"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:customers"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:customers"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/PerPage"
                    },
                    {
                        "$ref": "#/components/parameters/Cursor"
                    },
                    {
                        "$ref": "#/components/parameters/PageNumber"
                    },
                    {
                        "$ref": "#/components/parameters/ViewSummary"
                    },
                    {
                        "name": "filter[contact_role_id]",
                        "in": "query",
                        "description": "Assigned contact role. Use the `null` operator to list contacts with no role.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[contact_role_id][in]",
                        "in": "query",
                        "description": "Assigned contact role. Use the `null` operator to list contacts with no role. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[contact_role_id][null]",
                        "in": "query",
                        "description": "Assigned contact role. Use the `null` operator to list contacts with no role. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[contactable_id]",
                        "in": "query",
                        "description": "Key of the parent record. This pair is how a customer's contacts are listed: there is no `/customers/{customer}/contacts` path.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        },
                        "x-plnnrly-constraint": "Send contactable_id and contactable_type together."
                    },
                    {
                        "name": "filter[contactable_id][in]",
                        "in": "query",
                        "description": "Key of the parent record. This pair is how a customer's contacts are listed: there is no `/customers/{customer}/contacts` path. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false,
                        "x-plnnrly-constraint": "Send contactable_id and contactable_type together."
                    },
                    {
                        "name": "filter[contactable_type]",
                        "in": "query",
                        "description": "Kind of parent the contact hangs off. Must be sent together with `filter[contactable_id]`; one half without the other is a `400`.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "x-extensible-enum": true
                        },
                        "x-plnnrly-constraint": "Send contactable_type and contactable_id together."
                    },
                    {
                        "name": "filter[created_at]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][gte]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][lt]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][gte]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][lt]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ContactSort"
                    },
                    {
                        "name": "q",
                        "in": "query",
                        "description": "Free-text search over `name`, `email`, in that order of relevance. At most one substring match per request: `q` together with a `contains` filter is 400 invalid-query-parameter.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "minLength": 1
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ContactInclude"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "A page of records.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record.",
                                "schema": {
                                    "type": "string",
                                    "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/ContactList"
                                            }
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/ContactsIndexMeta"
                                        },
                                        "links": {
                                            "$ref": "#/components/schemas/PageLinks"
                                        }
                                    },
                                    "required": [
                                        "data",
                                        "meta",
                                        "links"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "One customer's contacts",
                        "query": {
                            "filter[contactable_type]": "customer",
                            "filter[contactable_id]": 1,
                            "include": "role"
                        }
                    }
                ],
                "x-plnnrly-permission": "customers::read",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1,
                    "view_detail_total": 3,
                    "surcharges": {
                        "include": 1,
                        "q": 2
                    }
                },
                "x-plnnrly-token-ability": [
                    "api:read:customers"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/ContactList",
                    "detail": "#/components/schemas/Contact"
                }
            },
            "post": {
                "operationId": "api.v2.contacts.store",
                "summary": "Create a contact",
                "description": "Creates a contact under a customer or a user. `contactable_type` and `contactable_id` are required together: the type is an alias (customer or user) and the id must name a record the caller may read, which for a user additionally requires the users prefix. Returns `201` with a `Location` header.",
                "tags": [
                    "Customers"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:customers"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:customers"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "contactable_type": {
                                        "type": "string",
                                        "enum": [
                                            "customer",
                                            "user"
                                        ],
                                        "description": "The record this contact belongs to: a customer for a CRM contact, a user for an employee's emergency contact. Rendered as {\"id\": N} until named in `?include=contactable`, and still as {\"id\": N} where the caller may not read the parent's prefix. `contactable_type` and `contactable_id` are always present, so the parent can be found without the include, and the reference form shows nothing `contactable_id` does not."
                                    },
                                    "contactable_id": {
                                        "type": "integer",
                                        "x-plnnrly-constraint": "Required when contactable_type is present.",
                                        "description": "The record this contact belongs to: a customer for a CRM contact, a user for an employee's emergency contact. Rendered as {\"id\": N} until named in `?include=contactable`, and still as {\"id\": N} where the caller may not read the parent's prefix. `contactable_type` and `contactable_id` are always present, so the parent can be found without the include, and the reference form shows nothing `contactable_id` does not."
                                    },
                                    "name": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "description": "Contact name. This is the default sort key and one of the fields `?q=` searches."
                                    },
                                    "email": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "format": "email",
                                        "maxLength": 255,
                                        "description": "Email address. The second field `?q=` searches; not unique."
                                    },
                                    "phone": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 255,
                                        "description": "Phone number."
                                    },
                                    "contact_role_id": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "x-plnnrly-references": {
                                            "resource": "ContactRole",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "customers",
                                        "description": "Assigned contact role, or `null`. Deleting a role unassigns it rather than refusing."
                                    },
                                    "address": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 255,
                                        "description": "Street address. plnnrly pre-fills this from the customer when adding a contact, but the API fills in no default for it."
                                    },
                                    "zip": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 255,
                                        "description": "Postal code."
                                    },
                                    "city": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 255,
                                        "description": "City."
                                    },
                                    "country": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 255,
                                        "description": "Country as an ISO 3166-1 alpha-2 code, e.g. NL, as plnnrly writes it. Older records may hold any string."
                                    },
                                    "position": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "description": "Display order within one parent, ascending. Null where unset."
                                    },
                                    "comment": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "description": "Free-form note, sanitised HTML. The emergency-contact form uses it for the relationship to the employee, e.g. Partner."
                                    }
                                },
                                "required": [
                                    "contactable_type",
                                    "contactable_id",
                                    "name"
                                ],
                                "additionalProperties": false
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Created.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "Idempotency-Replayed": {
                                "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise.",
                                "schema": {
                                    "type": "boolean",
                                    "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise."
                                }
                            },
                            "Location": {
                                "description": "The absolute URL of the created or queued resource.",
                                "schema": {
                                    "type": "string",
                                    "description": "The absolute URL of the created or queued resource."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Contact"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "$ref": "#/components/responses/Problem409Idempotency"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "A contact on a customer",
                        "body": {
                            "contactable_type": "customer",
                            "contactable_id": 1,
                            "name": "Marieke de Vries",
                            "email": "marieke@example.test",
                            "phone": "+31 20 123 4567",
                            "comment": "<p>Prefers email.</p>"
                        }
                    }
                ],
                "x-plnnrly-permission": "customers::create",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:customers"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/ContactList",
                    "detail": "#/components/schemas/Contact"
                }
            },
            "x-plnnrly-allow": [
                "GET",
                "POST"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/contacts/{contact}": {
            "get": {
                "operationId": "api.v2.contacts.show",
                "summary": "Read a contact",
                "description": "Returns one contact in the detail view. The response carries a strong `ETag` derived from `updated_at`; send it back as `If-None-Match` to get a `304`.",
                "tags": [
                    "Customers"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:customers"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:customers"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "contact",
                        "in": "path",
                        "description": "The Contact's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Contact"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "One contact",
                        "path": {
                            "contact": 1
                        },
                        "query": {
                            "include": "contactable,role"
                        }
                    }
                ],
                "x-plnnrly-permission": "customers::read",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:read:customers"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/ContactList",
                    "detail": "#/components/schemas/Contact"
                }
            },
            "patch": {
                "operationId": "api.v2.contacts.update",
                "summary": "Update a contact",
                "description": "Partially updates a contact: absent keys are untouched and an empty body is a `422`. `contactable_type` and `contactable_id` are not accepted: re-parenting a contact is not supported, so sending either is a `422`. Send `If-Match` with the `ETag` from your last read to detect a concurrent edit, which answers `412`.",
                "tags": [
                    "Customers"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:customers"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:customers"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "contact",
                        "in": "path",
                        "description": "The Contact's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "description": "Contact name. This is the default sort key and one of the fields `?q=` searches."
                                    },
                                    "email": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "format": "email",
                                        "maxLength": 255,
                                        "description": "Email address. The second field `?q=` searches; not unique."
                                    },
                                    "phone": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 255,
                                        "description": "Phone number."
                                    },
                                    "contact_role_id": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "x-plnnrly-references": {
                                            "resource": "ContactRole",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "customers",
                                        "description": "Assigned contact role, or `null`. Deleting a role unassigns it rather than refusing."
                                    },
                                    "address": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 255,
                                        "description": "Street address. plnnrly pre-fills this from the customer when adding a contact, but the API fills in no default for it."
                                    },
                                    "zip": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 255,
                                        "description": "Postal code."
                                    },
                                    "city": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 255,
                                        "description": "City."
                                    },
                                    "country": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 255,
                                        "description": "Country as an ISO 3166-1 alpha-2 code, e.g. NL, as plnnrly writes it. Older records may hold any string."
                                    },
                                    "position": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "description": "Display order within one parent, ascending. Null where unset."
                                    },
                                    "comment": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "description": "Free-form note, sanitised HTML. The emergency-contact form uses it for the relationship to the employee, e.g. Partner."
                                    }
                                },
                                "additionalProperties": false,
                                "minProperties": 1
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The updated record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Contact"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Correct a phone number",
                        "path": {
                            "contact": 1
                        },
                        "body": {
                            "phone": "+31 20 765 4321"
                        },
                        "headers": {
                            "If-Match": "*"
                        }
                    }
                ],
                "x-plnnrly-permission": "customers::update",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:customers"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/ContactList",
                    "detail": "#/components/schemas/Contact"
                }
            },
            "delete": {
                "operationId": "api.v2.contacts.destroy",
                "summary": "Delete a contact",
                "description": "Permanently deletes a contact and returns `204`. Contacts are not soft-deleted, so this cannot be reversed, there is no force parameter and there is no restore action.",
                "tags": [
                    "Customers"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:destroy:customers"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "contact",
                        "in": "path",
                        "description": "The Contact's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "responses": {
                    "204": {
                        "description": "Deleted. No body.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Delete a contact",
                        "path": {
                            "contact": 1
                        }
                    }
                ],
                "x-plnnrly-permission": "customers::delete",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:destroy:customers"
                ]
            },
            "x-plnnrly-allow": [
                "GET",
                "PATCH",
                "DELETE"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/customer-categories": {
            "get": {
                "operationId": "api.v2.customer-categories.index",
                "summary": "List customer categories",
                "description": "Returns the customer-category taxonomy, sorted by name A to Z. Each row carries the number of customers in the category and a link to that filtered customer collection. Search with `q=` over the category name.",
                "tags": [
                    "Customers"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:customer_categories"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:customer_categories"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/PerPage"
                    },
                    {
                        "$ref": "#/components/parameters/Cursor"
                    },
                    {
                        "$ref": "#/components/parameters/PageNumber"
                    },
                    {
                        "$ref": "#/components/parameters/ViewSummary"
                    },
                    {
                        "name": "filter[created_at]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][gte]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][lt]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][gte]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][lt]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/CustomerCategorySort"
                    },
                    {
                        "name": "q",
                        "in": "query",
                        "description": "Free-text search over `name`. At most one substring match per request: `q` together with a `contains` filter is 400 invalid-query-parameter.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "minLength": 1
                        }
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "A page of records.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record.",
                                "schema": {
                                    "type": "string",
                                    "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/CustomerCategoryList"
                                            }
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/CustomerCategoriesIndexMeta"
                                        },
                                        "links": {
                                            "$ref": "#/components/schemas/PageLinks"
                                        }
                                    },
                                    "required": [
                                        "data",
                                        "meta",
                                        "links"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, max-age=60",
                "x-plnnrly-examples": [
                    {
                        "name": "Find the enterprise categories",
                        "query": {
                            "q": "Enterprise",
                            "sort": "name"
                        }
                    }
                ],
                "x-plnnrly-permission": "customer_categories::read",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1,
                    "view_detail_total": 3,
                    "surcharges": {
                        "q": 2
                    }
                },
                "x-plnnrly-token-ability": [
                    "api:read:customer_categories"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/CustomerCategoryList",
                    "detail": "#/components/schemas/CustomerCategory"
                }
            },
            "post": {
                "operationId": "api.v2.customer-categories.store",
                "summary": "Create a customer category",
                "description": "Creates a category and returns it in the detail view with a `Location` header. Names are not required to be unique, as in plnnrly.",
                "tags": [
                    "Customers"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:customer_categories"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:customer_categories"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "description": "Category name as shown in the UI, e.g. Enterprise."
                                    },
                                    "description": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "description": "Free-text note on what belongs in this category."
                                    }
                                },
                                "required": [
                                    "name"
                                ],
                                "additionalProperties": false
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Created.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "Idempotency-Replayed": {
                                "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise.",
                                "schema": {
                                    "type": "boolean",
                                    "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise."
                                }
                            },
                            "Location": {
                                "description": "The absolute URL of the created or queued resource.",
                                "schema": {
                                    "type": "string",
                                    "description": "The absolute URL of the created or queued resource."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/CustomerCategory"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "$ref": "#/components/responses/Problem409Idempotency"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "A category for the largest accounts",
                        "body": {
                            "name": "Enterprise",
                            "description": "Accounts over 500 seats."
                        }
                    }
                ],
                "x-plnnrly-permission": "customer_categories::create",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:customer_categories"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/CustomerCategoryList",
                    "detail": "#/components/schemas/CustomerCategory"
                }
            },
            "x-plnnrly-allow": [
                "GET",
                "POST"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/customer-categories/{customer_category}": {
            "get": {
                "operationId": "api.v2.customer-categories.show",
                "summary": "Read a customer category",
                "description": "Returns one category in the detail view. Categories are hard-deleted, so a deleted category is gone and answers `404`.",
                "tags": [
                    "Customers"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:customer_categories"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:customer_categories"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "customer_category",
                        "in": "path",
                        "description": "The CustomerCategory's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/CustomerCategory"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, max-age=60",
                "x-plnnrly-examples": [
                    {
                        "name": "One category",
                        "path": {
                            "customer_category": 1
                        }
                    }
                ],
                "x-plnnrly-permission": "customer_categories::read",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:read:customer_categories"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/CustomerCategoryList",
                    "detail": "#/components/schemas/CustomerCategory"
                }
            },
            "patch": {
                "operationId": "api.v2.customer-categories.update",
                "summary": "Update a customer category",
                "description": "Partially updates a category: absent keys are untouched. Send `If-Match` with the `ETag` from your last read to detect a concurrent edit, which answers `412`.",
                "tags": [
                    "Customers"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:customer_categories"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:customer_categories"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "customer_category",
                        "in": "path",
                        "description": "The CustomerCategory's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "description": "Category name as shown in the UI, e.g. Enterprise."
                                    },
                                    "description": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "description": "Free-text note on what belongs in this category."
                                    }
                                },
                                "additionalProperties": false,
                                "minProperties": 1
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The updated record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/CustomerCategory"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Rename a category",
                        "path": {
                            "customer_category": 1
                        },
                        "body": {
                            "name": "Key accounts"
                        },
                        "headers": {
                            "If-Match": "*"
                        }
                    }
                ],
                "x-plnnrly-permission": "customer_categories::update",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:customer_categories"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/CustomerCategoryList",
                    "detail": "#/components/schemas/CustomerCategory"
                }
            },
            "delete": {
                "operationId": "api.v2.customer-categories.destroy",
                "summary": "Delete a customer category",
                "description": "Permanently deletes a category: categories are not soft-deleted and there is no restore. Customers in the category are un-categorised rather than blocked, so a category that is in use deletes successfully.",
                "tags": [
                    "Customers"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:destroy:customer_categories"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "customer_category",
                        "in": "path",
                        "description": "The CustomerCategory's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "responses": {
                    "204": {
                        "description": "Deleted. No body.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Delete a category",
                        "path": {
                            "customer_category": 1
                        }
                    }
                ],
                "x-plnnrly-permission": "customer_categories::delete",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:destroy:customer_categories"
                ]
            },
            "x-plnnrly-allow": [
                "GET",
                "PATCH",
                "DELETE"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/customer-subscriptions": {
            "get": {
                "operationId": "api.v2.customer-subscriptions.index",
                "summary": "List customer subscriptions",
                "description": "Returns the organisation's customer subscriptions, newest start date first. Defaults to the summary view; `view=detail` carries the same fields on this resource (no field of it is detail-only) and costs three read units instead of one. `filter[status]=active` with `filter[auto_renew]=true` and `filter[next_invoice_date][lte]=<today>` is the due-for-invoicing query the renewal run uses. Search with `q=` over the subscription description, and pass `trashed=with|only` to see deleted subscriptions. There is no relationship sort: use `sort=customer_id` or `filter[customer_id]` rather than `customer.name`.",
                "tags": [
                    "Billing"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:customer_subscriptions"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:customer_subscriptions"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/PerPage"
                    },
                    {
                        "$ref": "#/components/parameters/Cursor"
                    },
                    {
                        "$ref": "#/components/parameters/PageNumber"
                    },
                    {
                        "$ref": "#/components/parameters/ViewSummary"
                    },
                    {
                        "$ref": "#/components/parameters/Trashed"
                    },
                    {
                        "name": "filter[auto_renew]",
                        "in": "query",
                        "description": "Whether the renewal run may invoice the subscription again.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[billing_interval]",
                        "in": "query",
                        "description": "Interval the subscription is invoiced at.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "yearly",
                                "monthly",
                                "one-time"
                            ],
                            "x-extensible-enum": true
                        }
                    },
                    {
                        "name": "filter[billing_interval][ne]",
                        "in": "query",
                        "description": "Interval the subscription is invoiced at.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "yearly",
                                "monthly",
                                "one-time"
                            ],
                            "x-extensible-enum": true
                        }
                    },
                    {
                        "name": "filter[billing_interval][in]",
                        "in": "query",
                        "description": "Interval the subscription is invoiced at. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "string",
                                "enum": [
                                    "yearly",
                                    "monthly",
                                    "one-time"
                                ],
                                "x-extensible-enum": true
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[cancelled_at]",
                        "in": "query",
                        "description": "Day the subscription was cancelled. `null` selects the ones that were not.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[cancelled_at][gte]",
                        "in": "query",
                        "description": "Day the subscription was cancelled. `null` selects the ones that were not.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[cancelled_at][lte]",
                        "in": "query",
                        "description": "Day the subscription was cancelled. `null` selects the ones that were not.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[cancelled_at][null]",
                        "in": "query",
                        "description": "Day the subscription was cancelled. `null` selects the ones that were not. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[created_at]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][gte]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][lt]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[customer_id]",
                        "in": "query",
                        "description": "Customer the subscription belongs to. This is how you list one customer's subscriptions: there is no nested path for them.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[customer_id][in]",
                        "in": "query",
                        "description": "Customer the subscription belongs to. This is how you list one customer's subscriptions: there is no nested path for them. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[expires_at]",
                        "in": "query",
                        "description": "Day the subscription runs out. `null` selects the open-ended ones.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[expires_at][gte]",
                        "in": "query",
                        "description": "Day the subscription runs out. `null` selects the open-ended ones.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[expires_at][lte]",
                        "in": "query",
                        "description": "Day the subscription runs out. `null` selects the open-ended ones.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[expires_at][null]",
                        "in": "query",
                        "description": "Day the subscription runs out. `null` selects the open-ended ones. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[last_invoiced_at]",
                        "in": "query",
                        "description": "Day the subscription was last invoiced. `null` selects the ones that never were.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[last_invoiced_at][gte]",
                        "in": "query",
                        "description": "Day the subscription was last invoiced. `null` selects the ones that never were.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[last_invoiced_at][lte]",
                        "in": "query",
                        "description": "Day the subscription was last invoiced. `null` selects the ones that never were.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[last_invoiced_at][null]",
                        "in": "query",
                        "description": "Day the subscription was last invoiced. `null` selects the ones that never were. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[next_invoice_date]",
                        "in": "query",
                        "description": "First day of the period to be invoiced next. `null` selects the subscriptions the renewal run skips. Combined with `filter[status]=active` and `filter[auto_renew]=true` this is the due-for-invoicing query the renewal run itself uses, and it is the single most useful query on this resource.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[next_invoice_date][gte]",
                        "in": "query",
                        "description": "First day of the period to be invoiced next. `null` selects the subscriptions the renewal run skips. Combined with `filter[status]=active` and `filter[auto_renew]=true` this is the due-for-invoicing query the renewal run itself uses, and it is the single most useful query on this resource.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[next_invoice_date][lte]",
                        "in": "query",
                        "description": "First day of the period to be invoiced next. `null` selects the subscriptions the renewal run skips. Combined with `filter[status]=active` and `filter[auto_renew]=true` this is the due-for-invoicing query the renewal run itself uses, and it is the single most useful query on this resource.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[next_invoice_date][null]",
                        "in": "query",
                        "description": "First day of the period to be invoiced next. `null` selects the subscriptions the renewal run skips. Combined with `filter[status]=active` and `filter[auto_renew]=true` this is the due-for-invoicing query the renewal run itself uses, and it is the single most useful query on this resource. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[starts_at]",
                        "in": "query",
                        "description": "First day the subscription runs from. Combine gte and lte to select a period, e.g. the subscriptions that started in one month.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[starts_at][gte]",
                        "in": "query",
                        "description": "First day the subscription runs from. Combine gte and lte to select a period, e.g. the subscriptions that started in one month.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[starts_at][lte]",
                        "in": "query",
                        "description": "First day the subscription runs from. Combine gte and lte to select a period, e.g. the subscriptions that started in one month.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[status]",
                        "in": "query",
                        "description": "Lifecycle status. Only `active` subscriptions are invoiced by the renewal run.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "pending",
                                "active",
                                "cancelled",
                                "expired"
                            ],
                            "x-extensible-enum": true
                        }
                    },
                    {
                        "name": "filter[status][ne]",
                        "in": "query",
                        "description": "Lifecycle status. Only `active` subscriptions are invoiced by the renewal run.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "pending",
                                "active",
                                "cancelled",
                                "expired"
                            ],
                            "x-extensible-enum": true
                        }
                    },
                    {
                        "name": "filter[status][in]",
                        "in": "query",
                        "description": "Lifecycle status. Only `active` subscriptions are invoiced by the renewal run. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "string",
                                "enum": [
                                    "pending",
                                    "active",
                                    "cancelled",
                                    "expired"
                                ],
                                "x-extensible-enum": true
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[subscription_product_id]",
                        "in": "query",
                        "description": "Subscriptions with at least one line selling this subscription product, which is the Products filter in plnnrly. Lines that bill a hosting domain or a server are not products and never match.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[subscription_product_id][in]",
                        "in": "query",
                        "description": "Subscriptions with at least one line selling this subscription product, which is the Products filter in plnnrly. Lines that bill a hosting domain or a server are not products and never match. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[subscription_type_id]",
                        "in": "query",
                        "description": "Subscription type the subscription is an instance of.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[subscription_type_id][in]",
                        "in": "query",
                        "description": "Subscription type the subscription is an instance of. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[updated_at]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][gte]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][lt]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/CustomerSubscriptionSort"
                    },
                    {
                        "name": "q",
                        "in": "query",
                        "description": "Free-text search over `description`. At most one substring match per request: `q` together with a `contains` filter is 400 invalid-query-parameter.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "minLength": 1
                        }
                    },
                    {
                        "$ref": "#/components/parameters/CustomerSubscriptionInclude"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "A page of records.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record.",
                                "schema": {
                                    "type": "string",
                                    "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/CustomerSubscriptionList"
                                            }
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/CustomerSubscriptionsIndexMeta"
                                        },
                                        "links": {
                                            "$ref": "#/components/schemas/PageLinks"
                                        }
                                    },
                                    "required": [
                                        "data",
                                        "meta",
                                        "links"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Active subscriptions due to be invoiced this year",
                        "query": {
                            "filter[status]": "active",
                            "filter[auto_renew]": "true",
                            "filter[next_invoice_date][lte]": "2026-12-31",
                            "sort": "-starts_at"
                        }
                    }
                ],
                "x-plnnrly-feature": "billing",
                "x-plnnrly-permission": "customer_subscriptions::read",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1,
                    "view_detail_total": 3,
                    "surcharges": {
                        "include": 1,
                        "q": 2
                    }
                },
                "x-plnnrly-token-ability": [
                    "api:read:customer_subscriptions"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/CustomerSubscriptionList",
                    "detail": "#/components/schemas/CustomerSubscription"
                }
            },
            "post": {
                "operationId": "api.v2.customer-subscriptions.store",
                "summary": "Create a customer subscription",
                "description": "Creates a subscription and returns it in the detail view with a `Location` header. Its lines are not created here: nested writes are not accepted, so send `items` as separate POSTs to `customer-subscriptions/{customer_subscription}/items`; a subscription with no lines is valid and simply invoices nothing. `starts_at` is required: it has no default. Sending `auto_renew=true` together with `billing_interval=one-time` succeeds and comes back as `auto_renew=false`, because a one-off has no next period.",
                "tags": [
                    "Billing"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:customer_subscriptions"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:customer_subscriptions"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "customer_id": {
                                        "type": "integer",
                                        "x-plnnrly-references": {
                                            "resource": "Customer",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "customers",
                                        "description": "Customer this subscription belongs to."
                                    },
                                    "subscription_type_id": {
                                        "type": "integer",
                                        "x-plnnrly-references": {
                                            "resource": "SubscriptionType",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "subscription_types",
                                        "description": "Subscription type this subscription is an instance of."
                                    },
                                    "description": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "description": "What this subscription covers, as free text. This is the field `?q=` searches, and it is also what a generated invoice line falls back to."
                                    },
                                    "status": {
                                        "type": "string",
                                        "enum": [
                                            "pending",
                                            "active",
                                            "cancelled",
                                            "expired"
                                        ],
                                        "x-enum-descriptions": {
                                            "active": "Running, and the only status the renewal run invoices.",
                                            "cancelled": "Ended early; kept for history and never invoiced again.",
                                            "expired": "Ran to the end of its term and was not renewed.",
                                            "pending": "Agreed but not yet running; the renewal run ignores it."
                                        },
                                        "x-extensible-enum": true,
                                        "description": "Lifecycle status. Only an active subscription is picked up by the renewal run; the other three are inert."
                                    },
                                    "billing_interval": {
                                        "type": "string",
                                        "enum": [
                                            "yearly",
                                            "monthly",
                                            "one-time"
                                        ],
                                        "x-enum-descriptions": {
                                            "monthly": "Billed every month.",
                                            "one-time": "Charged once, on the invoice the product is first put on, and never renewed.",
                                            "yearly": "Billed once a year."
                                        },
                                        "x-extensible-enum": true,
                                        "description": "How often the subscription is invoiced. one-time forces `auto_renew` to `false`, because a one-off has no next period to move on to."
                                    },
                                    "starts_at": {
                                        "type": "string",
                                        "format": "date",
                                        "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                                        "description": "First day the subscription runs from, as a zone-free calendar date. Newest first is the default sort."
                                    },
                                    "next_invoice_date": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "format": "date",
                                        "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                                        "description": "First day of the period to be invoiced next. Omit it on create and the server fills it with `starts_at`, so the first period is billable; the renewal run advances it by one interval each time it invoices. Null makes the subscription invisible to that run (it is skipped, silently), so a subscription that should never auto-invoice is expressed this way."
                                    },
                                    "cancelled_at": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "format": "date",
                                        "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                                        "description": "Day the subscription was cancelled, `null` while it has not been. It is a record of the cancellation date only: nothing derives status from it."
                                    },
                                    "expires_at": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "format": "date",
                                        "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                                        "description": "Day the subscription runs out, `null` when it is open-ended."
                                    },
                                    "auto_renew": {
                                        "type": "boolean",
                                        "description": "Whether the renewal run may invoice this subscription again. A one-time subscription is always `false`: it is forced on every save, so sending `true` together with `billing_interval=one-time` succeeds and comes back as `false` rather than failing."
                                    }
                                },
                                "required": [
                                    "customer_id",
                                    "subscription_type_id",
                                    "starts_at"
                                ],
                                "additionalProperties": false
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Created.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "Idempotency-Replayed": {
                                "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise.",
                                "schema": {
                                    "type": "boolean",
                                    "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise."
                                }
                            },
                            "Location": {
                                "description": "The absolute URL of the created or queued resource.",
                                "schema": {
                                    "type": "string",
                                    "description": "The absolute URL of the created or queued resource."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/CustomerSubscription"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "$ref": "#/components/responses/Problem409Idempotency"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "A yearly hosting subscription",
                        "body": {
                            "customer_id": 1,
                            "subscription_type_id": 1,
                            "description": "Managed hosting and maintenance.",
                            "billing_interval": "yearly",
                            "starts_at": "2026-07-01",
                            "auto_renew": true
                        }
                    }
                ],
                "x-plnnrly-feature": "billing",
                "x-plnnrly-permission": "customer_subscriptions::create",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:customer_subscriptions"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/CustomerSubscriptionList",
                    "detail": "#/components/schemas/CustomerSubscription"
                }
            },
            "x-plnnrly-allow": [
                "GET",
                "POST"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/customer-subscriptions/{customer_subscription}": {
            "get": {
                "operationId": "api.v2.customer-subscriptions.show",
                "summary": "Read a customer subscription",
                "description": "Returns one subscription in the detail view. Subscriptions are soft-deleted, so a deleted subscription is `404` unless `trashed=with` or `trashed=only` is passed; `trashed=only` 404s a live subscription. Its lines are not embedded: follow `links.items`, and its invoices are at `sales-invoices?filter[customer_subscription_id]={id}`.",
                "tags": [
                    "Billing"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:customer_subscriptions"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:customer_subscriptions"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "customer_subscription",
                        "in": "path",
                        "description": "The CustomerSubscription's numeric id. Accepts a soft-deleted record.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/Trashed"
                    },
                    {
                        "$ref": "#/components/parameters/Meta"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/CustomerSubscription"
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/CustomerSubscriptionsShowMeta"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "One subscription with its customer",
                        "path": {
                            "customer_subscription": 1
                        },
                        "query": {
                            "include": "customer,subscription_type"
                        }
                    }
                ],
                "x-plnnrly-feature": "billing",
                "x-plnnrly-permission": "customer_subscriptions::read",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:read:customer_subscriptions"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/CustomerSubscriptionList",
                    "detail": "#/components/schemas/CustomerSubscription"
                }
            },
            "patch": {
                "operationId": "api.v2.customer-subscriptions.update",
                "summary": "Update a customer subscription",
                "description": "Partially updates a subscription: absent keys are untouched. Send `If-Match` with the `ETag` from your last read to detect a concurrent edit, which answers `412`. Switching `billing_interval` to one-time also sets `auto_renew` to `false`, whatever is sent alongside it; this holds on every save. Clearing `next_invoice_date` to `null` takes the subscription out of the renewal run without cancelling it. Lines are edited through their own sub-resource, never from here.",
                "tags": [
                    "Billing"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:customer_subscriptions"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:customer_subscriptions"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "customer_subscription",
                        "in": "path",
                        "description": "The CustomerSubscription's numeric id. Accepts a soft-deleted record.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/Trashed"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "customer_id": {
                                        "type": "integer",
                                        "x-plnnrly-references": {
                                            "resource": "Customer",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "customers",
                                        "description": "Customer this subscription belongs to."
                                    },
                                    "subscription_type_id": {
                                        "type": "integer",
                                        "x-plnnrly-references": {
                                            "resource": "SubscriptionType",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "subscription_types",
                                        "description": "Subscription type this subscription is an instance of."
                                    },
                                    "description": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "description": "What this subscription covers, as free text. This is the field `?q=` searches, and it is also what a generated invoice line falls back to."
                                    },
                                    "status": {
                                        "type": "string",
                                        "enum": [
                                            "pending",
                                            "active",
                                            "cancelled",
                                            "expired"
                                        ],
                                        "x-enum-descriptions": {
                                            "active": "Running, and the only status the renewal run invoices.",
                                            "cancelled": "Ended early; kept for history and never invoiced again.",
                                            "expired": "Ran to the end of its term and was not renewed.",
                                            "pending": "Agreed but not yet running; the renewal run ignores it."
                                        },
                                        "x-extensible-enum": true,
                                        "description": "Lifecycle status. Only an active subscription is picked up by the renewal run; the other three are inert."
                                    },
                                    "billing_interval": {
                                        "type": "string",
                                        "enum": [
                                            "yearly",
                                            "monthly",
                                            "one-time"
                                        ],
                                        "x-enum-descriptions": {
                                            "monthly": "Billed every month.",
                                            "one-time": "Charged once, on the invoice the product is first put on, and never renewed.",
                                            "yearly": "Billed once a year."
                                        },
                                        "x-extensible-enum": true,
                                        "description": "How often the subscription is invoiced. one-time forces `auto_renew` to `false`, because a one-off has no next period to move on to."
                                    },
                                    "starts_at": {
                                        "type": "string",
                                        "format": "date",
                                        "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                                        "description": "First day the subscription runs from, as a zone-free calendar date. Newest first is the default sort."
                                    },
                                    "next_invoice_date": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "format": "date",
                                        "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                                        "description": "First day of the period to be invoiced next. Omit it on create and the server fills it with `starts_at`, so the first period is billable; the renewal run advances it by one interval each time it invoices. Null makes the subscription invisible to that run (it is skipped, silently), so a subscription that should never auto-invoice is expressed this way."
                                    },
                                    "cancelled_at": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "format": "date",
                                        "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                                        "description": "Day the subscription was cancelled, `null` while it has not been. It is a record of the cancellation date only: nothing derives status from it."
                                    },
                                    "expires_at": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "format": "date",
                                        "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                                        "description": "Day the subscription runs out, `null` when it is open-ended."
                                    },
                                    "auto_renew": {
                                        "type": "boolean",
                                        "description": "Whether the renewal run may invoice this subscription again. A one-time subscription is always `false`: it is forced on every save, so sending `true` together with `billing_interval=one-time` succeeds and comes back as `false` rather than failing."
                                    }
                                },
                                "additionalProperties": false,
                                "minProperties": 1
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The updated record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/CustomerSubscription"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Cancel a subscription at the end of its term",
                        "path": {
                            "customer_subscription": 1
                        },
                        "body": {
                            "status": "cancelled",
                            "cancelled_at": "2026-09-30",
                            "auto_renew": false
                        },
                        "headers": {
                            "If-Match": "*"
                        }
                    }
                ],
                "x-plnnrly-feature": "billing",
                "x-plnnrly-permission": "customer_subscriptions::update",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:customer_subscriptions"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/CustomerSubscriptionList",
                    "detail": "#/components/schemas/CustomerSubscription"
                }
            },
            "delete": {
                "operationId": "api.v2.customer-subscriptions.destroy",
                "summary": "Delete a customer subscription",
                "description": "Soft-deletes a subscription: it is only marked deleted, and POST `customer-subscriptions/{customer_subscription}/actions/restore` reverses it. There is no permanent delete: a subscription that has ever been invoiced is referenced by those sales invoices, which is why `?force=` is rejected. A deleted subscription is skipped by the renewal run. Its lines are not deleted with it: they stay attached and come back with the subscription when it is restored. Sales invoices already raised from it are untouched and keep pointing at it.",
                "tags": [
                    "Billing"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:destroy:customer_subscriptions"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "customer_subscription",
                        "in": "path",
                        "description": "The CustomerSubscription's numeric id. Accepts a soft-deleted record.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "responses": {
                    "204": {
                        "description": "Deleted. No body.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Delete a subscription",
                        "path": {
                            "customer_subscription": 1
                        }
                    }
                ],
                "x-plnnrly-feature": "billing",
                "x-plnnrly-permission": "customer_subscriptions::delete",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:destroy:customer_subscriptions"
                ]
            },
            "x-plnnrly-allow": [
                "GET",
                "PATCH",
                "DELETE"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/customer-subscriptions/{customer_subscription}/actions/restore": {
            "post": {
                "operationId": "api.v2.customer-subscriptions.actions.restore",
                "summary": "Restore a deleted customer subscription",
                "description": "Clears the soft-delete marker and returns the subscription in the detail view. Restoring additionally requires `customer_subscriptions::delete` as well as `customer_subscriptions::create`. Restoring a subscription that is not deleted succeeds and returns an `already_in_target_state` warning. The restore finds a deleted subscription on its own, so the trashed parameter is not accepted. The subscription's lines are not touched: they were soft-deleted separately, if at all, and are restored through their own sub-resource.",
                "tags": [
                    "Billing"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:customer_subscriptions"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:customer_subscriptions"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "customer_subscription",
                        "in": "path",
                        "description": "The CustomerSubscription's numeric id. Accepts a soft-deleted record.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The record after the action ran.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "Idempotency-Replayed": {
                                "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise.",
                                "schema": {
                                    "type": "boolean",
                                    "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/CustomerSubscription"
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/CustomerSubscriptionsActionsRestoreMeta"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "$ref": "#/components/responses/Problem409Idempotency"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Restore a deleted subscription",
                        "path": {
                            "customer_subscription": 1
                        },
                        "body": {}
                    }
                ],
                "x-plnnrly-feature": "billing",
                "x-plnnrly-permission": "customer_subscriptions::create",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:customer_subscriptions"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/CustomerSubscriptionList",
                    "detail": "#/components/schemas/CustomerSubscription"
                }
            },
            "x-plnnrly-allow": [
                "POST"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/customer-subscriptions/{customer_subscription}/items": {
            "get": {
                "operationId": "api.v2.customer-subscriptions.items.index",
                "summary": "List a subscription's lines",
                "description": "Returns one customer subscription's lines in their printed order, `sort_order` ascending. There is no organisation-wide collection of subscription lines: a line only means anything under the subscription it belongs to, so this nested path is the only way to list them and `filter[customer_subscription_id]` does not exist. A deleted subscription answers `404` for the whole path unless `trashed=with` is passed, which also includes the subscription's deleted lines. Defaults to the summary view; pass `include=customer_subscription` or `include=subscribable` to expand what each line points at.",
                "tags": [
                    "Billing"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:customer_subscriptions"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:customer_subscriptions"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "customer_subscription",
                        "in": "path",
                        "description": "The CustomerSubscription's numeric id. Accepts a soft-deleted record.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/PerPage"
                    },
                    {
                        "$ref": "#/components/parameters/Cursor"
                    },
                    {
                        "$ref": "#/components/parameters/SubscriptionItemPageNumber"
                    },
                    {
                        "$ref": "#/components/parameters/ViewSummary"
                    },
                    {
                        "$ref": "#/components/parameters/Trashed"
                    },
                    {
                        "name": "filter[created_at]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][gte]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][lt]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[subscribable_id]",
                        "in": "query",
                        "description": "Id of the record this line sells. Send together with `subscribable_type`.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        },
                        "x-plnnrly-constraint": "Send subscribable_id and subscribable_type together."
                    },
                    {
                        "name": "filter[subscribable_id][in]",
                        "in": "query",
                        "description": "Id of the record this line sells. Send together with `subscribable_type`. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false,
                        "x-plnnrly-constraint": "Send subscribable_id and subscribable_type together."
                    },
                    {
                        "name": "filter[subscribable_id][null]",
                        "in": "query",
                        "description": "Id of the record this line sells. Send together with `subscribable_type`. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        },
                        "x-plnnrly-constraint": "Send subscribable_id and subscribable_type together."
                    },
                    {
                        "name": "filter[subscribable_type]",
                        "in": "query",
                        "description": "What kind of record this line sells. Send together with `subscribable_id`.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "x-extensible-enum": true
                        },
                        "x-plnnrly-constraint": "Send subscribable_type and subscribable_id together."
                    },
                    {
                        "name": "filter[updated_at]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][gte]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][lt]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/SubscriptionItemSort"
                    },
                    {
                        "name": "q",
                        "in": "query",
                        "description": "Free-text search over `description`. At most one substring match per request: `q` together with a `contains` filter is 400 invalid-query-parameter.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "minLength": 1
                        }
                    },
                    {
                        "$ref": "#/components/parameters/SubscriptionItemInclude"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "A page of records.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record.",
                                "schema": {
                                    "type": "string",
                                    "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/SubscriptionItemList"
                                            }
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/CustomerSubscriptionsItemsIndexMeta"
                                        },
                                        "links": {
                                            "$ref": "#/components/schemas/PageLinks"
                                        }
                                    },
                                    "required": [
                                        "data",
                                        "meta",
                                        "links"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "One subscription's lines, including its deleted ones",
                        "path": {
                            "customer_subscription": 1
                        },
                        "query": {
                            "trashed": "with"
                        }
                    }
                ],
                "x-plnnrly-feature": "billing",
                "x-plnnrly-permission": "customer_subscriptions::read",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1,
                    "view_detail_total": 3,
                    "surcharges": {
                        "include": 1,
                        "q": 2
                    }
                },
                "x-plnnrly-token-ability": [
                    "api:read:customer_subscriptions"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/SubscriptionItemList",
                    "detail": "#/components/schemas/SubscriptionItem"
                }
            },
            "post": {
                "operationId": "api.v2.customer-subscriptions.items.store",
                "summary": "Add a line to a subscription",
                "description": "Creates one line on the subscription named in the path and returns it in the detail view, with a `Location` header pointing at the flat item path. The subscription comes from the path, so `customer_subscription_id` is not accepted in the body and a line can never be moved between subscriptions. `sort_order` is assigned by the server and appends the line to the end. `vat_amount` and `total_amount` are computed by the server from quantity, `sales_price` and `vat_rate` and are never accepted; note that `total_amount` includes VAT here, unlike a sales-invoice row. `subscribable_type` and `subscribable_id` are sent together or not at all, and `subscription_product` is the only type this API can attach: lines that bill a hosting domain or a server can be created in plnnrly only. A deleted subscription answers `404`. `exact_item_id` and the other Exact codes are optional: a renewal falls back to the product's own Exact values and then to the organisation's defaults.",
                "tags": [
                    "Billing"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:customer_subscriptions"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:customer_subscriptions"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "customer_subscription",
                        "in": "path",
                        "description": "The CustomerSubscription's numeric id. Accepts a soft-deleted record.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "description": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "description": "The line text, as printed on the invoice a renewal generates. May contain placeholders such as {{$domain}}; the API publishes the raw stored text and those are resolved only at invoice time."
                                    },
                                    "quantity": {
                                        "type": "integer",
                                        "minimum": 1,
                                        "description": "Number of units billed per period."
                                    },
                                    "purchase_price": {
                                        "type": "string",
                                        "format": "decimal",
                                        "pattern": "^\\d+(\\.\\d{1,2})?$",
                                        "x-plnnrly-constraint": "At least 0. At most 99999999.99.",
                                        "x-plnnrly-maximum": 99999999.99,
                                        "x-plnnrly-minimum": 0,
                                        "description": "Cost price per unit, for margin bookkeeping only. It feeds neither generated amount below and appears on no invoice."
                                    },
                                    "sales_price": {
                                        "type": "string",
                                        "format": "decimal",
                                        "pattern": "^\\d+(\\.\\d{1,2})?$",
                                        "x-plnnrly-constraint": "At least 0. At most 99999999.99.",
                                        "x-plnnrly-maximum": 99999999.99,
                                        "x-plnnrly-minimum": 0,
                                        "description": "Sales price per unit, excluding VAT. This is what actually bills: a renewal copies it into the generated invoice row's unit price."
                                    },
                                    "vat_rate": {
                                        "type": "integer",
                                        "minimum": 0,
                                        "maximum": 100,
                                        "description": "VAT percentage as a whole number: 21 means 21%."
                                    },
                                    "subscribable_type": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "enum": [
                                            "subscription_product",
                                            null
                                        ],
                                        "x-plnnrly-constraint": "Required when subscribable_id is present.",
                                        "description": "What this line sells. Rendered as {\"id\": N} until named in `?include=subscribable`, which additionally requires the read permission and token scope of the `subscription_products` prefix. Lines that bill a hosting domain or a server carry a `subscribable_type` this API publishes no shape for, and keep the {\"id\": N} form however they are requested; identify those from `subscribable_type` and `subscribable_id` yourself. Null on both halves is a free-text line priced by hand."
                                    },
                                    "subscribable_id": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "x-plnnrly-constraint": "Required when subscribable_type is present.",
                                        "x-plnnrly-references": {
                                            "resource": "SubscriptionProduct",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "subscription_products",
                                        "description": "What this line sells. Rendered as {\"id\": N} until named in `?include=subscribable`, which additionally requires the read permission and token scope of the `subscription_products` prefix. Lines that bill a hosting domain or a server carry a `subscribable_type` this API publishes no shape for, and keep the {\"id\": N} form however they are requested; identify those from `subscribable_type` and `subscribable_id` yourself. Null on both halves is a free-text line priced by hand."
                                    },
                                    "exact_item_id": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 255,
                                        "description": "Exact Online article identifier this line bills against. Optional: a renewal falls back to the subscription product's own Exact values and then to the organisation's Exact defaults, so leaving it empty does not break the sync."
                                    },
                                    "exact_vat_code": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 255,
                                        "description": "Exact Online VAT code, by code rather than by id."
                                    },
                                    "exact_gl_account": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 255,
                                        "description": "Exact Online general-ledger revenue account for this line."
                                    },
                                    "exact_cost_center": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 255,
                                        "description": "Exact Online cost-centre code."
                                    }
                                },
                                "additionalProperties": false,
                                "minProperties": 1
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Created.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "Idempotency-Replayed": {
                                "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise.",
                                "schema": {
                                    "type": "boolean",
                                    "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise."
                                }
                            },
                            "Location": {
                                "description": "The absolute URL of the created or queued resource.",
                                "schema": {
                                    "type": "string",
                                    "description": "The absolute URL of the created or queued resource."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/SubscriptionItem"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "$ref": "#/components/responses/Problem409Idempotency"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "A hosting line billing a subscription product",
                        "path": {
                            "customer_subscription": 1
                        },
                        "body": {
                            "description": "Webhosting {{$domain}}",
                            "quantity": 1,
                            "purchase_price": "4.50",
                            "sales_price": "12.50",
                            "subscribable_type": "subscription_product",
                            "subscribable_id": 1
                        }
                    }
                ],
                "x-plnnrly-feature": "billing",
                "x-plnnrly-permission": "customer_subscriptions::create",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:customer_subscriptions"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/SubscriptionItemList",
                    "detail": "#/components/schemas/SubscriptionItem"
                }
            },
            "x-plnnrly-allow": [
                "GET",
                "POST"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/customers": {
            "get": {
                "operationId": "api.v2.customers.index",
                "summary": "List customers",
                "description": "Returns the customers the caller may read, by name A-Z. Defaults to the summary view; `view=detail` carries the same fields on this resource (no field of it is detail-only) and costs three read units instead of one. Search with `q=` over name, code and email, filter on the five reference ids, the status, the VAT flag and the three dates, and pass `trashed=with|only` to see deleted customers. `filter[account_manager_id]=me` is the \"my customers\" query. There is no relationship sort: `sort=customer_category.name` is a `400`.",
                "tags": [
                    "Customers"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:customers"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:customers"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/PerPage"
                    },
                    {
                        "$ref": "#/components/parameters/Cursor"
                    },
                    {
                        "$ref": "#/components/parameters/PageNumber"
                    },
                    {
                        "$ref": "#/components/parameters/ViewSummary"
                    },
                    {
                        "$ref": "#/components/parameters/Trashed"
                    },
                    {
                        "name": "filter[account_manager_id]",
                        "in": "query",
                        "description": "Responsible account manager. Accepts the literal `me`. Use the `null` operator to list customers with no account manager.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[account_manager_id][in]",
                        "in": "query",
                        "description": "Responsible account manager. Accepts the literal `me`. Use the `null` operator to list customers with no account manager. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[account_manager_id][null]",
                        "in": "query",
                        "description": "Responsible account manager. Accepts the literal `me`. Use the `null` operator to list customers with no account manager. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[company_branch_id]",
                        "in": "query",
                        "description": "Industry branch.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[company_branch_id][in]",
                        "in": "query",
                        "description": "Industry branch. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[company_branch_id][null]",
                        "in": "query",
                        "description": "Industry branch. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[company_size_id]",
                        "in": "query",
                        "description": "Company size band.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[company_size_id][in]",
                        "in": "query",
                        "description": "Company size band. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[company_size_id][null]",
                        "in": "query",
                        "description": "Company size band. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[company_type_id]",
                        "in": "query",
                        "description": "Company type.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[company_type_id][in]",
                        "in": "query",
                        "description": "Company type. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[company_type_id][null]",
                        "in": "query",
                        "description": "Company type. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[created_at]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][gte]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][lt]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[customer_category_id]",
                        "in": "query",
                        "description": "Customer category. Use the `null` operator to list uncategorised customers.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[customer_category_id][in]",
                        "in": "query",
                        "description": "Customer category. Use the `null` operator to list uncategorised customers. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[customer_category_id][null]",
                        "in": "query",
                        "description": "Customer category. Use the `null` operator to list uncategorised customers. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[customer_since]",
                        "in": "query",
                        "description": "Day this customer became a customer.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[customer_since][gte]",
                        "in": "query",
                        "description": "Day this customer became a customer.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[customer_since][lte]",
                        "in": "query",
                        "description": "Day this customer became a customer.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[customer_since][null]",
                        "in": "query",
                        "description": "Day this customer became a customer. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[exact_keep_local_address]",
                        "in": "query",
                        "description": "Whether the Exact sync leaves this customer's local address alone.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[exact_synced_at]",
                        "in": "query",
                        "description": "When the Exact sync last handled this customer. Use the `null` operator to list customers the sync has never touched.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[exact_synced_at][gte]",
                        "in": "query",
                        "description": "When the Exact sync last handled this customer. Use the `null` operator to list customers the sync has never touched.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[exact_synced_at][lte]",
                        "in": "query",
                        "description": "When the Exact sync last handled this customer. Use the `null` operator to list customers the sync has never touched.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[exact_synced_at][null]",
                        "in": "query",
                        "description": "When the Exact sync last handled this customer. Use the `null` operator to list customers the sync has never touched. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[founded_at]",
                        "in": "query",
                        "description": "Day the customer's company was founded.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[founded_at][gte]",
                        "in": "query",
                        "description": "Day the customer's company was founded.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[founded_at][lte]",
                        "in": "query",
                        "description": "Day the customer's company was founded.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[founded_at][null]",
                        "in": "query",
                        "description": "Day the customer's company was founded. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[is_vat_exempt]",
                        "in": "query",
                        "description": "Whether invoices to this customer are issued without VAT.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[status]",
                        "in": "query",
                        "description": "Where the customer sits in the sales lifecycle.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "suspect",
                                "prospect",
                                "future-opportunity",
                                "active",
                                "inactive"
                            ],
                            "x-extensible-enum": true
                        }
                    },
                    {
                        "name": "filter[status][ne]",
                        "in": "query",
                        "description": "Where the customer sits in the sales lifecycle.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "suspect",
                                "prospect",
                                "future-opportunity",
                                "active",
                                "inactive"
                            ],
                            "x-extensible-enum": true
                        }
                    },
                    {
                        "name": "filter[status][in]",
                        "in": "query",
                        "description": "Where the customer sits in the sales lifecycle. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "string",
                                "enum": [
                                    "suspect",
                                    "prospect",
                                    "future-opportunity",
                                    "active",
                                    "inactive"
                                ],
                                "x-extensible-enum": true
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[updated_at]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][gte]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][lt]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/CustomerSort"
                    },
                    {
                        "name": "q",
                        "in": "query",
                        "description": "Free-text search over `name`, `code`, `email`, in that order of relevance. At most one substring match per request: `q` together with a `contains` filter is 400 invalid-query-parameter.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "minLength": 1
                        }
                    },
                    {
                        "$ref": "#/components/parameters/CustomerInclude"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "A page of records.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record.",
                                "schema": {
                                    "type": "string",
                                    "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/CustomerList"
                                            }
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/CustomersIndexMeta"
                                        },
                                        "links": {
                                            "$ref": "#/components/schemas/PageLinks"
                                        }
                                    },
                                    "required": [
                                        "data",
                                        "meta",
                                        "links"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "My active customers, oldest relationships first",
                        "query": {
                            "filter[status]": "active",
                            "filter[account_manager_id]": "me",
                            "sort": "customer_since",
                            "include": "customer_category,tags"
                        }
                    }
                ],
                "x-plnnrly-permission": "customers::read",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1,
                    "view_detail_total": 3,
                    "surcharges": {
                        "include": 1,
                        "include_to_many": 3,
                        "q": 2
                    }
                },
                "x-plnnrly-token-ability": [
                    "api:read:customers"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/CustomerList",
                    "detail": "#/components/schemas/Customer"
                }
            },
            "post": {
                "operationId": "api.v2.customers.store",
                "summary": "Create a customer",
                "description": "Creates a customer and returns it in the detail view with a `Location` header. name, code, color, status and `account_manager_id` are required: the first four have no default, and an account manager is mandatory on the customer form in plnnrly. code must be unique. color must be a CSS hex triplet or sextet; any other value would read back as #6b7280. `tag_ids` replaces the whole tag set and may be omitted. Sending address, zip or city geocodes the address server-side and fills lat and lng, which are not writable. `debtor_code` additionally requires `billing_settings::update`.",
                "tags": [
                    "Customers"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:customers"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:customers"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "description": "Customer name. This is the default sort key and one of the fields `?q=` searches."
                                    },
                                    "code": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "x-plnnrly-constraint": "Must be unique across Customer.",
                                        "x-plnnrly-unique": {
                                            "resource": "Customer",
                                            "field": "code"
                                        },
                                        "description": "Short customer code, e.g. ACM. Prefixes every work item's issue key, is enforced unique on write, and is one of the fields `?q=` searches."
                                    },
                                    "color": {
                                        "type": "string",
                                        "pattern": "^#[0-9a-fA-F]{3}(?:[0-9a-fA-F]{3})?\\z",
                                        "description": "Chip colour as a CSS hex triplet or sextet, e.g. #1d4ed8. A stored value that is not valid hex reads back as #6b7280."
                                    },
                                    "status": {
                                        "type": "string",
                                        "enum": [
                                            "suspect",
                                            "prospect",
                                            "future-opportunity",
                                            "active",
                                            "inactive"
                                        ],
                                        "x-enum-descriptions": {
                                            "active": "A paying customer with work or a subscription running.",
                                            "future-opportunity": "A real opportunity, parked until a later moment.",
                                            "inactive": "A former customer, kept for history and no longer pursued.",
                                            "prospect": "A qualified lead being actively pursued towards a first deal.",
                                            "suspect": "An unqualified lead; nobody has established a need yet."
                                        },
                                        "x-extensible-enum": true,
                                        "description": "Where the customer sits in the sales lifecycle."
                                    },
                                    "email": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "format": "email",
                                        "maxLength": 255,
                                        "description": "Primary email address. The third field `?q=` searches; not unique."
                                    },
                                    "phone": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 255,
                                        "description": "Phone number."
                                    },
                                    "address": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 255,
                                        "description": "Street address. Writing this, zip or city re-geocodes lat and lng."
                                    },
                                    "zip": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 255,
                                        "description": "Postal code."
                                    },
                                    "city": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 255,
                                        "description": "City."
                                    },
                                    "country": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 255,
                                        "description": "Country as an ISO 3166-1 alpha-2 code, e.g. NL, as plnnrly writes it. Older records may hold any string."
                                    },
                                    "founded_at": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "format": "date",
                                        "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                                        "description": "Day the customer's company was founded."
                                    },
                                    "customer_since": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "format": "date",
                                        "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                                        "description": "Day this customer became a customer."
                                    },
                                    "is_vat_exempt": {
                                        "type": "boolean",
                                        "description": "Whether invoices to this customer are issued without VAT."
                                    },
                                    "debtor_code": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 255,
                                        "description": "Debtor number in the accounting system. Writing it requires `billing_settings::update`."
                                    },
                                    "account_manager_id": {
                                        "type": "integer",
                                        "x-plnnrly-references": {
                                            "resource": "User",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "users",
                                        "description": "Responsible account manager. Required on create; unassigned when the user is deleted."
                                    },
                                    "customer_category_id": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "x-plnnrly-references": {
                                            "resource": "CustomerCategory",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "customer_categories",
                                        "description": "Customer category, or `null`."
                                    },
                                    "company_branch_id": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "x-plnnrly-references": {
                                            "resource": "CompanyBranch",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "company_branches",
                                        "description": "Industry branch, or `null`."
                                    },
                                    "company_type_id": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "x-plnnrly-references": {
                                            "field": "id"
                                        },
                                        "description": "Company type, or `null`. Company types are your organisation's own list, maintained in plnnrly and not exposed as a collection."
                                    },
                                    "company_size_id": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "x-plnnrly-references": {
                                            "field": "id"
                                        },
                                        "description": "Company size band, or `null`. Company sizes are your organisation's own list, maintained in plnnrly and not exposed as a collection."
                                    },
                                    "tag_ids": {
                                        "type": "array",
                                        "items": {
                                            "type": "integer",
                                            "x-plnnrly-references": {
                                                "resource": "Tag",
                                                "field": "id"
                                            }
                                        },
                                        "maxItems": 50,
                                        "description": "Tags on this customer. A customer takes at most 50 tags, so the include lists them all. `tag_ids` replaces the whole set."
                                    }
                                },
                                "required": [
                                    "name",
                                    "code",
                                    "color",
                                    "status",
                                    "account_manager_id"
                                ],
                                "additionalProperties": false
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Created.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "Idempotency-Replayed": {
                                "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise.",
                                "schema": {
                                    "type": "boolean",
                                    "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise."
                                }
                            },
                            "Location": {
                                "description": "The absolute URL of the created or queued resource.",
                                "schema": {
                                    "type": "string",
                                    "description": "The absolute URL of the created or queued resource."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Customer"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "$ref": "#/components/responses/Problem409Idempotency"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "A new active customer",
                        "body": {
                            "name": "Acme Industries",
                            "code": "ACM",
                            "color": "#1d4ed8",
                            "status": "active",
                            "email": "hello@acme.test",
                            "address": "Keizersgracht 1",
                            "zip": "1015 CJ",
                            "city": "Amsterdam",
                            "country": "NL",
                            "customer_since": "2026-01-15",
                            "account_manager_id": 1
                        }
                    }
                ],
                "x-plnnrly-permission": "customers::create",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:customers"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/CustomerList",
                    "detail": "#/components/schemas/Customer"
                }
            },
            "x-plnnrly-allow": [
                "GET",
                "POST"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/customers/{customer}": {
            "get": {
                "operationId": "api.v2.customers.show",
                "summary": "Read a customer",
                "description": "Returns one customer in the detail view. The response carries a strong `ETag` derived from `updated_at`; send it back as `If-None-Match` to get a `304`. A trashed customer is `404` unless `trashed=with` is passed, which needs only `customers::read`.",
                "tags": [
                    "Customers"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:customers"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:customers"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "customer",
                        "in": "path",
                        "description": "The Customer's numeric id. Accepts a soft-deleted record.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/Trashed"
                    },
                    {
                        "$ref": "#/components/parameters/Meta"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Customer"
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/CustomersShowMeta"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "One customer with its category and tags",
                        "path": {
                            "customer": 1
                        },
                        "query": {
                            "include": "customer_category,company_branch,tags"
                        }
                    }
                ],
                "x-plnnrly-permission": "customers::read",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:read:customers"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/CustomerList",
                    "detail": "#/components/schemas/Customer"
                }
            },
            "patch": {
                "operationId": "api.v2.customers.update",
                "summary": "Update a customer",
                "description": "Partially updates a customer: absent keys are untouched and an empty body is a `422`. `tag_ids` replaces the whole tag set, and an empty array clears it. Sending address, zip or city re-geocodes the address and overwrites lat and lng, neither of which is writable. `debtor_code` additionally requires `billing_settings::update`. Send `If-Match` with the `ETag` from your last read to detect a concurrent edit, which answers `412`.",
                "tags": [
                    "Customers"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:customers"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:customers"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "customer",
                        "in": "path",
                        "description": "The Customer's numeric id. Accepts a soft-deleted record.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/Trashed"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "description": "Customer name. This is the default sort key and one of the fields `?q=` searches."
                                    },
                                    "code": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "x-plnnrly-constraint": "Must be unique across Customer.",
                                        "x-plnnrly-unique": {
                                            "resource": "Customer",
                                            "field": "code"
                                        },
                                        "description": "Short customer code, e.g. ACM. Prefixes every work item's issue key, is enforced unique on write, and is one of the fields `?q=` searches."
                                    },
                                    "color": {
                                        "type": "string",
                                        "pattern": "^#[0-9a-fA-F]{3}(?:[0-9a-fA-F]{3})?\\z",
                                        "description": "Chip colour as a CSS hex triplet or sextet, e.g. #1d4ed8. A stored value that is not valid hex reads back as #6b7280."
                                    },
                                    "status": {
                                        "type": "string",
                                        "enum": [
                                            "suspect",
                                            "prospect",
                                            "future-opportunity",
                                            "active",
                                            "inactive"
                                        ],
                                        "x-enum-descriptions": {
                                            "active": "A paying customer with work or a subscription running.",
                                            "future-opportunity": "A real opportunity, parked until a later moment.",
                                            "inactive": "A former customer, kept for history and no longer pursued.",
                                            "prospect": "A qualified lead being actively pursued towards a first deal.",
                                            "suspect": "An unqualified lead; nobody has established a need yet."
                                        },
                                        "x-extensible-enum": true,
                                        "description": "Where the customer sits in the sales lifecycle."
                                    },
                                    "email": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "format": "email",
                                        "maxLength": 255,
                                        "description": "Primary email address. The third field `?q=` searches; not unique."
                                    },
                                    "phone": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 255,
                                        "description": "Phone number."
                                    },
                                    "address": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 255,
                                        "description": "Street address. Writing this, zip or city re-geocodes lat and lng."
                                    },
                                    "zip": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 255,
                                        "description": "Postal code."
                                    },
                                    "city": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 255,
                                        "description": "City."
                                    },
                                    "country": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 255,
                                        "description": "Country as an ISO 3166-1 alpha-2 code, e.g. NL, as plnnrly writes it. Older records may hold any string."
                                    },
                                    "founded_at": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "format": "date",
                                        "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                                        "description": "Day the customer's company was founded."
                                    },
                                    "customer_since": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "format": "date",
                                        "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                                        "description": "Day this customer became a customer."
                                    },
                                    "is_vat_exempt": {
                                        "type": "boolean",
                                        "description": "Whether invoices to this customer are issued without VAT."
                                    },
                                    "debtor_code": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 255,
                                        "description": "Debtor number in the accounting system. Writing it requires `billing_settings::update`."
                                    },
                                    "account_manager_id": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "x-plnnrly-references": {
                                            "resource": "User",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "users",
                                        "description": "Responsible account manager. Required on create; unassigned when the user is deleted."
                                    },
                                    "customer_category_id": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "x-plnnrly-references": {
                                            "resource": "CustomerCategory",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "customer_categories",
                                        "description": "Customer category, or `null`."
                                    },
                                    "company_branch_id": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "x-plnnrly-references": {
                                            "resource": "CompanyBranch",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "company_branches",
                                        "description": "Industry branch, or `null`."
                                    },
                                    "company_type_id": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "x-plnnrly-references": {
                                            "field": "id"
                                        },
                                        "description": "Company type, or `null`. Company types are your organisation's own list, maintained in plnnrly and not exposed as a collection."
                                    },
                                    "company_size_id": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "x-plnnrly-references": {
                                            "field": "id"
                                        },
                                        "description": "Company size band, or `null`. Company sizes are your organisation's own list, maintained in plnnrly and not exposed as a collection."
                                    },
                                    "tag_ids": {
                                        "type": "array",
                                        "items": {
                                            "type": "integer",
                                            "x-plnnrly-references": {
                                                "resource": "Tag",
                                                "field": "id"
                                            }
                                        },
                                        "maxItems": 50,
                                        "description": "Tags on this customer. A customer takes at most 50 tags, so the include lists them all. `tag_ids` replaces the whole set."
                                    }
                                },
                                "additionalProperties": false,
                                "minProperties": 1
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The updated record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Customer"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Mark a customer inactive",
                        "path": {
                            "customer": 1
                        },
                        "body": {
                            "status": "inactive"
                        },
                        "headers": {
                            "If-Match": "*"
                        }
                    }
                ],
                "x-plnnrly-permission": "customers::update",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:customers"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/CustomerList",
                    "detail": "#/components/schemas/Customer"
                }
            },
            "delete": {
                "operationId": "api.v2.customers.destroy",
                "summary": "Delete a customer",
                "description": "Soft-deletes a customer and returns `204`; `POST customers/{customer}/actions/restore` reverses it. The DELETE cascades to projects: a soft delete also soft-deletes every project of this customer, and actions/restore does not bring them back; restore each one through `POST projects/{project}/actions/restore`. There is no permanent delete: `force=true` answers `400` `unsupported-parameter`. Deleting an already-deleted customer answers `204`, so a retry is safe.",
                "tags": [
                    "Customers"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:destroy:customers"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "customer",
                        "in": "path",
                        "description": "The Customer's numeric id. Accepts a soft-deleted record.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "responses": {
                    "204": {
                        "description": "Deleted. No body.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Soft-delete a customer",
                        "path": {
                            "customer": 1
                        }
                    }
                ],
                "x-plnnrly-permission": "customers::delete",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:destroy:customers"
                ]
            },
            "x-plnnrly-allow": [
                "GET",
                "PATCH",
                "DELETE"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/customers/{customer}/actions/restore": {
            "post": {
                "operationId": "api.v2.customers.actions.restore",
                "summary": "Restore a deleted customer",
                "description": "Clears the soft-delete marker and returns the customer in the detail view. Restoring additionally requires `customers::delete` as well as `customers::create`. Restoring a customer that is not deleted succeeds and returns an `already_in_target_state` warning. The customer's projects are not restored: deleting the customer soft-deleted each of them, and each is brought back through `POST projects/{project}/actions/restore`. The restore finds a deleted customer on its own, so the trashed parameter is not accepted.",
                "tags": [
                    "Customers"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:customers"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:customers"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "customer",
                        "in": "path",
                        "description": "The Customer's numeric id. Accepts a soft-deleted record.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The record after the action ran.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "Idempotency-Replayed": {
                                "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise.",
                                "schema": {
                                    "type": "boolean",
                                    "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Customer"
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/CustomersActionsRestoreMeta"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "$ref": "#/components/responses/Problem409Idempotency"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Restore a deleted customer",
                        "path": {
                            "customer": 1
                        },
                        "body": {}
                    }
                ],
                "x-plnnrly-permission": "customers::create",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:customers"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/CustomerList",
                    "detail": "#/components/schemas/Customer"
                }
            },
            "x-plnnrly-allow": [
                "POST"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/drive-documents": {
            "get": {
                "operationId": "api.v2.drive-documents.index",
                "summary": "List document bodies",
                "description": "Returns the bodies of the drive documents whose owning drive item the caller may read, most recently written first. A body is visible exactly when the item it belongs to is: only the bodies of drive items you can read are listed, so this collection can be read across the whole organisation without leaking a document the caller cannot open. Narrow it to one item, or to a list of them, with `filter[drive_item_id]`. The body itself is not in the default summary view: `content` is detail-only, so pass `?view=detail` to get it, which costs 3 read units. Bodies served here may lag slightly behind a document that is being edited live, because the collection does not collect unsaved edits from the live editing session; `GET /api/v2/drive-documents/{drive_document}` does. `total` is omitted unless you pass `with_total=1`. There is no `q` on this resource and no soft delete, so neither `q` nor `trashed` is an accepted parameter. The declared permission is `project_drive::access`; `customer_drive`, `organization_drive` and `user_drive` apply by the owning item's owner type, and a token covering any of the four is accepted.",
                "tags": [
                    "Drive"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:project_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:read:customer_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:read:organization_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:read:user_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:project_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:customer_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:organization_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:user_drive"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/PerPage"
                    },
                    {
                        "$ref": "#/components/parameters/Cursor"
                    },
                    {
                        "$ref": "#/components/parameters/ViewSummary"
                    },
                    {
                        "$ref": "#/components/parameters/WithTotal"
                    },
                    {
                        "name": "filter[created_at]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][gte]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][lt]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[drive_item_id]",
                        "in": "query",
                        "description": "The drive item whose body you want. Accepts a list, so a client that listed drive items can fetch all their bodies in one call.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[drive_item_id][in]",
                        "in": "query",
                        "description": "The drive item whose body you want. Accepts a list, so a client that listed drive items can fetch all their bodies in one call. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[updated_at]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][gte]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][lt]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/DriveDocumentSort"
                    },
                    {
                        "$ref": "#/components/parameters/DriveDocumentInclude"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "A page of records.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record.",
                                "schema": {
                                    "type": "string",
                                    "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/DriveDocumentList"
                                            }
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/DriveDocumentsIndexMeta"
                                        },
                                        "links": {
                                            "$ref": "#/components/schemas/PageLinks"
                                        }
                                    },
                                    "required": [
                                        "data",
                                        "meta",
                                        "links"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "One drive item's body as Markdown",
                        "query": {
                            "filter[drive_item_id]": 1,
                            "view": "detail",
                            "with_total": 1
                        }
                    }
                ],
                "x-plnnrly-permission": "project_drive::access",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1,
                    "view_detail_total": 3,
                    "surcharges": {
                        "with_total": 2,
                        "include": 1
                    }
                },
                "x-plnnrly-token-ability": [
                    "api:read:project_drive",
                    "api:read:customer_drive",
                    "api:read:organization_drive",
                    "api:read:user_drive"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/DriveDocumentList",
                    "detail": "#/components/schemas/DriveDocument"
                }
            },
            "post": {
                "operationId": "api.v2.drive-documents.store",
                "summary": "Create a document body",
                "description": "Gives an existing bodyless drive item a body, and returns it with a `Location` header. This is the second half of creating a document: `POST /api/v2/drive-items` creates the item, and this endpoint fills it in. `content` is GitHub-Flavored Markdown and may be omitted for an empty document. Two refusals: an item that already has a body answers `409` `duplicate-resource`, because a drive item has at most one, and an item that is not an editable document (an uploaded PDF, image or Office file) answers `422` on `/drive_item_id`, since there is no editor schema to hold a body. An item the caller cannot read is refused exactly as a nonexistent one is. Because the body did not exist a moment ago, no collaboration room can be open on it, so the content is stored directly and the editor adopts it on first open. The declared permission is `project_drive::create`; the other three drive prefixes apply by the owning item's owner type, and the record rule, edit access on that item, is what actually decides.",
                "tags": [
                    "Drive"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:project_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:write:customer_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:write:organization_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:write:user_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:project_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:customer_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:organization_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:user_drive"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "drive_item_id": {
                                        "type": "integer",
                                        "x-plnnrly-references": {
                                            "resource": "DriveItem",
                                            "field": "id"
                                        },
                                        "description": "The drive item this body belongs to. Set on create and immutable afterwards; read the item itself from `GET /api/v2/drive-items/{drive_item}`."
                                    },
                                    "content": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 262144,
                                        "description": "The document body as GitHub-Flavored Markdown: headings, lists, task lists (`- [ ]`), tables, code fences, links and images all survive. Detail-only: pass `?view=detail` to get it on the collection. Markdown cannot express text colours, highlights, alignment or @mentions, so read `?include=content_html` first if the document may contain them: a read of `content` followed by a PATCH of it discards all four permanently."
                                    }
                                },
                                "required": [
                                    "drive_item_id"
                                ],
                                "additionalProperties": false
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Created.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "Idempotency-Replayed": {
                                "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise.",
                                "schema": {
                                    "type": "boolean",
                                    "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise."
                                }
                            },
                            "Location": {
                                "description": "The absolute URL of the created or queued resource.",
                                "schema": {
                                    "type": "string",
                                    "description": "The absolute URL of the created or queued resource."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/DriveDocument"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "description": "Conflict.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "oneOf": [
                                        {
                                            "$ref": "#/components/schemas/DuplicateResourceProblem"
                                        },
                                        {
                                            "$ref": "#/components/schemas/IdempotencyKeyInFlightProblem"
                                        },
                                        {
                                            "$ref": "#/components/schemas/IdempotencyKeyReusedProblem"
                                        }
                                    ],
                                    "discriminator": {
                                        "propertyName": "type",
                                        "mapping": {
                                            "https://developers.plnnrly.com/api/v2/problems/duplicate-resource": "#/components/schemas/DuplicateResourceProblem",
                                            "https://developers.plnnrly.com/api/v2/problems/idempotency-key-in-flight": "#/components/schemas/IdempotencyKeyInFlightProblem",
                                            "https://developers.plnnrly.com/api/v2/problems/idempotency-key-reused": "#/components/schemas/IdempotencyKeyReusedProblem"
                                        }
                                    }
                                },
                                "examples": {
                                    "duplicate-resource": {
                                        "$ref": "#/components/examples/DuplicateResourceProblem"
                                    },
                                    "idempotency-key-in-flight": {
                                        "$ref": "#/components/examples/IdempotencyKeyInFlightProblem"
                                    },
                                    "idempotency-key-reused": {
                                        "$ref": "#/components/examples/IdempotencyKeyReusedProblem"
                                    }
                                }
                            }
                        }
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "A body for a freshly created drive item",
                        "body": {
                            "drive_item_id": 1,
                            "content": "# Kick-off\n\n- [ ] Confirm scope with the client\n- [x] Book the room\n"
                        }
                    }
                ],
                "x-plnnrly-permission": "project_drive::create",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:project_drive",
                    "api:write:customer_drive",
                    "api:write:organization_drive",
                    "api:write:user_drive"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/DriveDocumentList",
                    "detail": "#/components/schemas/DriveDocument"
                }
            },
            "x-plnnrly-allow": [
                "GET",
                "POST"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/drive-documents/{drive_document}": {
            "get": {
                "operationId": "api.v2.drive-documents.show",
                "summary": "Read a document body",
                "description": "Returns one document body in the detail view: `content` as GitHub-Flavored Markdown, plus `?include=content_html` for the sanitised-HTML projection that also carries the colours, highlights, alignment and @mentions Markdown cannot express. Read that projection first if you intend to write the document back, because a Markdown round-trip discards all four. This endpoint first collects any edits still pending in a live editing session, so the body and the `ETag` describe what has actually been typed; if the collaboration server is unreachable it serves the last stored body instead of failing. The drive item's own metadata (title, folder, tags, owner, effective access) is not repeated here: read it from `GET /api/v2/drive-items/{drive_item}`, following `drive_item_id`. There is no soft delete on this resource, so neither `trashed` nor `force` is an accepted parameter. The declared permission is `project_drive::access`; the other three drive prefixes apply by the owning item's owner type, and the per-item rule is what actually decides.",
                "tags": [
                    "Drive"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:project_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:read:customer_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:read:organization_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:read:user_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:project_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:customer_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:organization_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:user_drive"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "drive_document",
                        "in": "path",
                        "description": "The DriveDocument's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/DriveDocument"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "One body, with the HTML projection alongside it",
                        "path": {
                            "drive_document": 1
                        },
                        "query": {
                            "include": "content_html"
                        }
                    }
                ],
                "x-plnnrly-permission": "project_drive::access",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:read:project_drive",
                    "api:read:customer_drive",
                    "api:read:organization_drive",
                    "api:read:user_drive"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/DriveDocumentList",
                    "detail": "#/components/schemas/DriveDocument"
                }
            },
            "patch": {
                "operationId": "api.v2.drive-documents.update",
                "summary": "Write a document body",
                "description": "Replaces or extends a document body. This is the one route in the API that writes one: the drive item itself carries no body. `content` is GitHub-Flavored Markdown, an empty body is rejected and any key other than `content` and `mode` is a `422`. By default `mode` is `replace`: the Markdown you send becomes the whole document, which overwrites whatever somebody else typed since your last read and discards, permanently, the text colours, highlights, alignment and @mentions Markdown cannot express; read `?include=content_html` on the document first if it may contain them. With `mode: append` the Markdown is added after the existing body and nothing already there is touched, which is the safe way to add notes to a shared document. The write goes through the live collaboration session, so anyone with the document open sees it immediately; if the collaboration server refuses the content, or is unavailable while the document is under collaborative editing, the write is refused with a `422` on `/content` rather than silently lost. Send `If-Match` with the `ETag` from your last read to detect a concurrent edit, which answers `412`. Treat it as best-effort only: this resource carries no revision number and `updated_at` is second-precision, so two writes inside the same second are indistinguishable. The declared permission is `project_drive::create` (editing is part of the create permission); the other three drive prefixes apply by the owning item's owner type, and the record rule is edit access on that item. Note that the owning item being archived does not block this write, even though the item itself then reads as `404` without `trashed=with`; restore the item first if you want plnnrly and the API to agree.",
                "tags": [
                    "Drive"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:project_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:write:customer_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:write:organization_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:write:user_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:project_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:customer_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:organization_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:user_drive"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "drive_document",
                        "in": "path",
                        "description": "The DriveDocument's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "content": {
                                        "type": "string",
                                        "maxLength": 262144,
                                        "description": "The document body as GitHub-Flavored Markdown: headings, lists, task lists (`- [ ]`), tables, code fences, links and images all survive. Detail-only: pass `?view=detail` to get it on the collection. Markdown cannot express text colours, highlights, alignment or @mentions, so read `?include=content_html` first if the document may contain them: a read of `content` followed by a PATCH of it discards all four permanently."
                                    },
                                    "mode": {
                                        "type": "string",
                                        "enum": [
                                            "replace",
                                            "append"
                                        ],
                                        "description": "Write-only, on update only: `replace` (the default) makes `content` the whole body; `append` adds it after the existing body and keeps everything already there, colours and @mentions included.",
                                        "writeOnly": true
                                    }
                                },
                                "required": [
                                    "content"
                                ],
                                "additionalProperties": false,
                                "minProperties": 1
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The updated record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/DriveDocument"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Replace the body",
                        "path": {
                            "drive_document": 1
                        },
                        "body": {
                            "content": "# Kick-off\n\nConfirmed for Monday 09:00.\n"
                        },
                        "headers": {
                            "If-Match": "*"
                        }
                    }
                ],
                "x-plnnrly-permission": "project_drive::create",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:project_drive",
                    "api:write:customer_drive",
                    "api:write:organization_drive",
                    "api:write:user_drive"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/DriveDocumentList",
                    "detail": "#/components/schemas/DriveDocument"
                }
            },
            "delete": {
                "operationId": "api.v2.drive-documents.destroy",
                "summary": "Delete a document body",
                "description": "Permanently discards a document body, leaving the drive item itself in place and bodyless; a new one can be created for it with `POST /api/v2/drive-documents`. This resource is hard-delete: there is no restore action, no `force` parameter and no `trashed` parameter. Archiving a document is a different operation and belongs to the item: DELETE `/api/v2/drive-items/{drive_item}`, which is reversible. Deleting an already-deleted body answers `404`. The todos derived from the body are removed afterwards. The declared permission is `project_drive::create` (the same permission as editing); the other three drive prefixes apply by the owning item's owner type, and the record rule is edit access on that item.",
                "tags": [
                    "Drive"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:destroy:project_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:destroy:customer_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:destroy:organization_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:destroy:user_drive"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "drive_document",
                        "in": "path",
                        "description": "The DriveDocument's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "responses": {
                    "204": {
                        "description": "Deleted. No body.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Discard a body",
                        "path": {
                            "drive_document": 1
                        }
                    }
                ],
                "x-plnnrly-permission": "project_drive::create",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:destroy:project_drive",
                    "api:destroy:customer_drive",
                    "api:destroy:organization_drive",
                    "api:destroy:user_drive"
                ]
            },
            "x-plnnrly-allow": [
                "GET",
                "PATCH",
                "DELETE"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/drive-folders": {
            "get": {
                "operationId": "api.v2.drive-folders.index",
                "summary": "List drive folders",
                "description": "Returns the drive folders the caller may read: the tree plnnrly's file browser draws. Access is decided per folder (the permission for the owner type, the access inherited from the nearest ancestor that does not inherit, then any per-folder grant), and it applies before any filter you send, so this collection can be read across the whole organisation without leaking a folder the caller cannot open. Narrow it to one drive with the `owner_type`/`owner_id` pair, which must be sent together, and to one level with `filter[parent_id]`; `filter[parent_id][null]=true` is the drive root, and a bare id is that folder's direct children, which is how a client walks the tree. The default name sort is stable across the organisation; the `order` sort is only meaningful once you have narrowed to one drive and one level, because positions are scoped per (`owner_type`, `owner_id`, `parent_id`). A folder is hard-deleted, so there is no trashed parameter. `total` is omitted unless you pass `with_total=1`. The declared permission is `project_drive::access`; `customer_drive`, `organization_drive` and `user_drive` apply by owner type, a token covering any of the four is accepted, and the per-folder rule is what actually decides.",
                "tags": [
                    "Drive"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:project_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:read:customer_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:read:organization_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:read:user_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:project_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:customer_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:organization_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:user_drive"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/PerPage"
                    },
                    {
                        "$ref": "#/components/parameters/Cursor"
                    },
                    {
                        "$ref": "#/components/parameters/ViewSummary"
                    },
                    {
                        "$ref": "#/components/parameters/WithTotal"
                    },
                    {
                        "name": "filter[created_at]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][gte]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][lt]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_by]",
                        "in": "query",
                        "description": "Author of the folder. Null on legacy rows.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[created_by][in]",
                        "in": "query",
                        "description": "Author of the folder. Null on legacy rows. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[created_by][null]",
                        "in": "query",
                        "description": "Author of the folder. Null on legacy rows. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[general_access]",
                        "in": "query",
                        "description": "Baseline sharing tier, which applies only while `inherits_permissions` is `false`.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "everyone_edit",
                                "everyone_view",
                                "restricted"
                            ],
                            "x-extensible-enum": true
                        }
                    },
                    {
                        "name": "filter[general_access][ne]",
                        "in": "query",
                        "description": "Baseline sharing tier, which applies only while `inherits_permissions` is `false`.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "everyone_edit",
                                "everyone_view",
                                "restricted"
                            ],
                            "x-extensible-enum": true
                        }
                    },
                    {
                        "name": "filter[general_access][in]",
                        "in": "query",
                        "description": "Baseline sharing tier, which applies only while `inherits_permissions` is `false`. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "string",
                                "enum": [
                                    "everyone_edit",
                                    "everyone_view",
                                    "restricted"
                                ],
                                "x-extensible-enum": true
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[inherits_permissions]",
                        "in": "query",
                        "description": "Whether the folder takes its permissions from its parent.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[owner_id]",
                        "in": "query",
                        "description": "Id of the record whose drive this is, within the `owner_type` given. Must be sent together with `owner_type`. For `organization` it is your own organisation's id.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        },
                        "x-plnnrly-constraint": "Send owner_id and owner_type together."
                    },
                    {
                        "name": "filter[owner_id][in]",
                        "in": "query",
                        "description": "Id of the record whose drive this is, within the `owner_type` given. Must be sent together with `owner_type`. For `organization` it is your own organisation's id. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false,
                        "x-plnnrly-constraint": "Send owner_id and owner_type together."
                    },
                    {
                        "name": "filter[owner_type]",
                        "in": "query",
                        "description": "Which drive the folder lives in: `project`, `customer`, `organization` or `user`. Must be sent together with `owner_id`.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "x-extensible-enum": true
                        },
                        "x-plnnrly-constraint": "Send owner_type and owner_id together."
                    },
                    {
                        "name": "filter[parent_id]",
                        "in": "query",
                        "description": "Folder this one sits in. Pass `null` to list the folders at the drive root, or an id to list one level of the tree.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[parent_id][in]",
                        "in": "query",
                        "description": "Folder this one sits in. Pass `null` to list the folders at the drive root, or an id to list one level of the tree. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[parent_id][null]",
                        "in": "query",
                        "description": "Folder this one sits in. Pass `null` to list the folders at the drive root, or an id to list one level of the tree. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[updated_at]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][gte]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][lt]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/DriveFolderSort"
                    },
                    {
                        "name": "q",
                        "in": "query",
                        "description": "Free-text search over `name`. At most one substring match per request: `q` together with a `contains` filter is 400 invalid-query-parameter.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "minLength": 1
                        }
                    },
                    {
                        "$ref": "#/components/parameters/DriveFolderInclude"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "A page of records.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record.",
                                "schema": {
                                    "type": "string",
                                    "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/DriveFolderList"
                                            }
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/DriveFoldersIndexMeta"
                                        },
                                        "links": {
                                            "$ref": "#/components/schemas/PageLinks"
                                        }
                                    },
                                    "required": [
                                        "data",
                                        "meta",
                                        "links"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "One project's drive root, with owners expanded",
                        "query": {
                            "filter[owner_type]": "project",
                            "filter[owner_id]": 1,
                            "filter[parent_id][null]": "true",
                            "include": "owner",
                            "with_total": 1
                        }
                    }
                ],
                "x-plnnrly-permission": "project_drive::access",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1,
                    "view_detail_total": 3,
                    "surcharges": {
                        "with_total": 2,
                        "include": 1,
                        "q": 2
                    }
                },
                "x-plnnrly-token-ability": [
                    "api:read:project_drive",
                    "api:read:customer_drive",
                    "api:read:organization_drive",
                    "api:read:user_drive"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/DriveFolderList",
                    "detail": "#/components/schemas/DriveFolder"
                }
            },
            "post": {
                "operationId": "api.v2.drive-folders.store",
                "summary": "Create a drive folder",
                "description": "Creates a folder under a project, a customer, the organisation drive or the caller's own drive (plnnrly's create-folder and create-subfolder actions) and returns it with a `Location` header. The drive is named by the `owner_type`/`owner_id` pair, this being one of the few flat creates that accepts a type and id pair, and a record the caller cannot read is refused exactly as a nonexistent one is. For `owner_type=organization` there is no `owner_id` to send: it is always your own organisation. A `parent_id` must belong to the same owner and be a folder you can edit; omit it, or send `null`, to create at the drive root. The new folder inherits its parent's permissions, and its sort position stays 0 until it is placed in plnnrly. The declared permission is `project_drive::create`; the other three drive prefixes apply by owner type, and the record rule is what actually decides.",
                "tags": [
                    "Drive"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:project_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:write:customer_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:write:organization_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:write:user_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:project_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:customer_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:organization_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:user_drive"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "owner_type": {
                                        "type": "string",
                                        "enum": [
                                            "project",
                                            "customer",
                                            "organization",
                                            "user"
                                        ],
                                        "description": "The project, customer, organisation or user whose drive this folder lives in. An organisation-owned folder renders as a bare {\"id\": N} reference."
                                    },
                                    "owner_id": {
                                        "type": "integer",
                                        "description": "The project, customer, organisation or user whose drive this folder lives in. An organisation-owned folder renders as a bare {\"id\": N} reference."
                                    },
                                    "name": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "description": "Folder name as shown in the drive tree."
                                    },
                                    "parent_id": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "x-plnnrly-references": {
                                            "resource": "DriveFolder",
                                            "field": "id"
                                        },
                                        "description": "Folder this one sits in, or `null` at the drive root."
                                    }
                                },
                                "required": [
                                    "owner_type",
                                    "owner_id",
                                    "name"
                                ],
                                "additionalProperties": false
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Created.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "Idempotency-Replayed": {
                                "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise.",
                                "schema": {
                                    "type": "boolean",
                                    "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise."
                                }
                            },
                            "Location": {
                                "description": "The absolute URL of the created or queued resource.",
                                "schema": {
                                    "type": "string",
                                    "description": "The absolute URL of the created or queued resource."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/DriveFolder"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "description": "Conflict.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "oneOf": [
                                        {
                                            "$ref": "#/components/schemas/IdempotencyKeyInFlightProblem"
                                        },
                                        {
                                            "$ref": "#/components/schemas/IdempotencyKeyReusedProblem"
                                        },
                                        {
                                            "$ref": "#/components/schemas/StateConflictProblem"
                                        }
                                    ],
                                    "discriminator": {
                                        "propertyName": "type",
                                        "mapping": {
                                            "https://developers.plnnrly.com/api/v2/problems/idempotency-key-in-flight": "#/components/schemas/IdempotencyKeyInFlightProblem",
                                            "https://developers.plnnrly.com/api/v2/problems/idempotency-key-reused": "#/components/schemas/IdempotencyKeyReusedProblem",
                                            "https://developers.plnnrly.com/api/v2/problems/state-conflict": "#/components/schemas/StateConflictProblem"
                                        }
                                    }
                                },
                                "examples": {
                                    "idempotency-key-in-flight": {
                                        "$ref": "#/components/examples/IdempotencyKeyInFlightProblem"
                                    },
                                    "idempotency-key-reused": {
                                        "$ref": "#/components/examples/IdempotencyKeyReusedProblem"
                                    },
                                    "state-conflict": {
                                        "$ref": "#/components/examples/StateConflictProblem"
                                    }
                                }
                            }
                        }
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "A folder at a project's drive root",
                        "body": {
                            "owner_type": "project",
                            "owner_id": 1,
                            "name": "Kick-off"
                        }
                    }
                ],
                "x-plnnrly-permission": "project_drive::create",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:project_drive",
                    "api:write:customer_drive",
                    "api:write:organization_drive",
                    "api:write:user_drive"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/DriveFolderList",
                    "detail": "#/components/schemas/DriveFolder"
                }
            },
            "x-plnnrly-allow": [
                "GET",
                "POST"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/drive-folders/{drive_folder}": {
            "get": {
                "operationId": "api.v2.drive-folders.show",
                "summary": "Read a drive folder",
                "description": "Returns one drive folder in the detail view. `access` is the calling user's own effective access level on it. `owner` and `parent_folder` render as references until named in include; an organisation-owned folder's owner is always a bare reference. The folder's contents are two other reads: `data.links.drive_items` for its items, and `GET /api/v2/drive-folders?filter[parent_id]={id}` for its subfolders. A folder is hard-deleted, so there is no trashed parameter. The declared permission is `project_drive::access`; the other three drive prefixes apply by owner type, and the per-folder rule is what actually decides.",
                "tags": [
                    "Drive"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:project_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:read:customer_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:read:organization_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:read:user_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:project_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:customer_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:organization_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:user_drive"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "drive_folder",
                        "in": "path",
                        "description": "The DriveFolder's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/DriveFolder"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "One folder with its owner and parent expanded",
                        "path": {
                            "drive_folder": 1
                        },
                        "query": {
                            "include": "owner,parent_folder"
                        }
                    }
                ],
                "x-plnnrly-permission": "project_drive::access",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:read:project_drive",
                    "api:read:customer_drive",
                    "api:read:organization_drive",
                    "api:read:user_drive"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/DriveFolderList",
                    "detail": "#/components/schemas/DriveFolder"
                }
            },
            "patch": {
                "operationId": "api.v2.drive-folders.update",
                "summary": "Update a drive folder",
                "description": "Partially updates a drive folder: absent keys are untouched, an empty body is rejected. `name` and `parent_id` are the writable keys: a rename, and a move to another folder within the same drive, which is plnnrly's folder drag-and-drop minus the position. The new parent must belong to the same owner, be a folder you can edit, and be neither this folder nor one of its descendants; `null` moves the folder to the drive root. Moving a folder to a different drive is not possible in v2.0: neither half of the owner pair is accepted on a PATCH, and neither are order, `created_by`, `general_access` or `inherits_permissions`. Send `If-Match` with the `ETag` from your last read to detect a concurrent edit, which answers `412`. Treat it as best-effort only: this resource carries no concurrency token and `updated_at` is second-precision, so two writes inside the same second are indistinguishable. The declared permission is `project_drive::create` (editing is part of the create permission), and it is a superset of what is checked: the record rule needs the owner type's `{owner}_drive::access` plus edit access on the folder, which is exactly what plnnrly enforces.",
                "tags": [
                    "Drive"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:project_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:write:customer_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:write:organization_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:write:user_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:project_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:customer_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:organization_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:user_drive"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "drive_folder",
                        "in": "path",
                        "description": "The DriveFolder's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "description": "Folder name as shown in the drive tree."
                                    },
                                    "parent_id": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "x-plnnrly-references": {
                                            "resource": "DriveFolder",
                                            "field": "id"
                                        },
                                        "description": "Folder this one sits in, or `null` at the drive root."
                                    }
                                },
                                "additionalProperties": false,
                                "minProperties": 1
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The updated record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/DriveFolder"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Rename a folder and move it to the drive root",
                        "path": {
                            "drive_folder": 1
                        },
                        "body": {
                            "name": "Kick-off (archive)",
                            "parent_id": null
                        },
                        "headers": {
                            "If-Match": "*"
                        }
                    }
                ],
                "x-plnnrly-permission": "project_drive::create",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:project_drive",
                    "api:write:customer_drive",
                    "api:write:organization_drive",
                    "api:write:user_drive"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/DriveFolderList",
                    "detail": "#/components/schemas/DriveFolder"
                }
            },
            "delete": {
                "operationId": "api.v2.drive-folders.destroy",
                "summary": "Delete a drive folder",
                "description": "Permanently deletes an empty drive folder. A folder holding a live drive item or a subfolder answers `409` `has_children`: plnnrly deletes any folder and moves its contents up, and the API deliberately does not, so move or delete the contents first. A folder is hard-deleted, so there is no restore and `force` is not an accepted parameter. One consequence worth planning for: only live items count, so a folder holding nothing but archived items is deletable, and those archived items land at the drive root rather than in this folder's parent. The declared permission is `project_drive::create`, the same permission as editing and a superset of what the record rule checks: `{owner}_drive::access` plus edit access on the folder.",
                "tags": [
                    "Drive"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:destroy:project_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:destroy:customer_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:destroy:organization_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:destroy:user_drive"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "drive_folder",
                        "in": "path",
                        "description": "The DriveFolder's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "responses": {
                    "204": {
                        "description": "Deleted. No body.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "description": "Conflict.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/StateConflictProblem"
                                },
                                "examples": {
                                    "state-conflict": {
                                        "$ref": "#/components/examples/StateConflictProblem"
                                    }
                                }
                            }
                        }
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Delete an empty folder",
                        "path": {
                            "drive_folder": 1
                        }
                    }
                ],
                "x-plnnrly-permission": "project_drive::create",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:destroy:project_drive",
                    "api:destroy:customer_drive",
                    "api:destroy:organization_drive",
                    "api:destroy:user_drive"
                ]
            },
            "x-plnnrly-allow": [
                "GET",
                "PATCH",
                "DELETE"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/drive-items": {
            "get": {
                "operationId": "api.v2.drive-items.index",
                "summary": "List drive items",
                "description": "Returns the drive items the caller may read: the rows plnnrly's file browser lists. Access is decided per item (the permission for the owner type, the access inherited from its folder, then any per-item grant), and it applies before any filter you send, so this collection can be read across the whole organisation without leaking an item the caller cannot open. Narrow it to one drive with the `owner_type`/`owner_id` pair, which must be sent together, and to one folder with `filter[folder_id]`; the default `order_column` sort is only meaningful once you have, because positions are scoped per (`owner_type`, `owner_id`, `folder_id`) and an organisation-wide sort by it interleaves every drive. Pass `trashed=with` or `trashed=only` to see archived items; that needs only this endpoint's permission, never the restore ability. `total` is omitted unless you pass `with_total=1`. The declared permission is `project_drive::access`; `customer_drive`, `organization_drive` and `user_drive` apply by owner type, and a token covering any of the four is accepted.",
                "tags": [
                    "Drive"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:project_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:read:customer_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:read:organization_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:read:user_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:project_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:customer_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:organization_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:user_drive"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/PerPage"
                    },
                    {
                        "$ref": "#/components/parameters/Cursor"
                    },
                    {
                        "$ref": "#/components/parameters/ViewSummary"
                    },
                    {
                        "$ref": "#/components/parameters/Trashed"
                    },
                    {
                        "$ref": "#/components/parameters/WithTotal"
                    },
                    {
                        "name": "filter[archived_by]",
                        "in": "query",
                        "description": "The user who archived the item. Pair it with `trashed=only` for an archive view.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[archived_by][in]",
                        "in": "query",
                        "description": "The user who archived the item. Pair it with `trashed=only` for an archive view. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[archived_by][null]",
                        "in": "query",
                        "description": "The user who archived the item. Pair it with `trashed=only` for an archive view. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[content_record_id]",
                        "in": "query",
                        "description": "Id of the document body. Must be sent together with `content_record_type`.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        },
                        "x-plnnrly-constraint": "Send content_record_id and content_record_type together."
                    },
                    {
                        "name": "filter[content_record_id][in]",
                        "in": "query",
                        "description": "Id of the document body. Must be sent together with `content_record_type`. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false,
                        "x-plnnrly-constraint": "Send content_record_id and content_record_type together."
                    },
                    {
                        "name": "filter[content_record_id][null]",
                        "in": "query",
                        "description": "Id of the document body. Must be sent together with `content_record_type`. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        },
                        "x-plnnrly-constraint": "Send content_record_id and content_record_type together."
                    },
                    {
                        "name": "filter[content_record_type]",
                        "in": "query",
                        "description": "Type of body the item points at. `drive_document` is the only value. Must be sent together with `content_record_id`.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "x-extensible-enum": true
                        },
                        "x-plnnrly-constraint": "Send content_record_type and content_record_id together."
                    },
                    {
                        "name": "filter[created_at]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][gte]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][lt]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_by]",
                        "in": "query",
                        "description": "Author of the item.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[created_by][in]",
                        "in": "query",
                        "description": "Author of the item. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[created_by][null]",
                        "in": "query",
                        "description": "Author of the item. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[folder_id]",
                        "in": "query",
                        "description": "Folder the item sits in. Pass `null` to list the items at the drive root.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[folder_id][in]",
                        "in": "query",
                        "description": "Folder the item sits in. Pass `null` to list the items at the drive root. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[folder_id][null]",
                        "in": "query",
                        "description": "Folder the item sits in. Pass `null` to list the items at the drive root. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[general_access]",
                        "in": "query",
                        "description": "Baseline sharing tier, which applies only while `inherits_permissions` is `false`.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "everyone_edit",
                                "everyone_view",
                                "restricted"
                            ],
                            "x-extensible-enum": true
                        }
                    },
                    {
                        "name": "filter[general_access][ne]",
                        "in": "query",
                        "description": "Baseline sharing tier, which applies only while `inherits_permissions` is `false`.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "everyone_edit",
                                "everyone_view",
                                "restricted"
                            ],
                            "x-extensible-enum": true
                        }
                    },
                    {
                        "name": "filter[general_access][in]",
                        "in": "query",
                        "description": "Baseline sharing tier, which applies only while `inherits_permissions` is `false`. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "string",
                                "enum": [
                                    "everyone_edit",
                                    "everyone_view",
                                    "restricted"
                                ],
                                "x-extensible-enum": true
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[inherits_permissions]",
                        "in": "query",
                        "description": "Whether the item takes its permissions from its folder.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[kind]",
                        "in": "query",
                        "description": "What the item is.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "document",
                                "pdf",
                                "image",
                                "audio",
                                "video",
                                "office",
                                "other"
                            ],
                            "x-extensible-enum": true
                        }
                    },
                    {
                        "name": "filter[kind][ne]",
                        "in": "query",
                        "description": "What the item is.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "document",
                                "pdf",
                                "image",
                                "audio",
                                "video",
                                "office",
                                "other"
                            ],
                            "x-extensible-enum": true
                        }
                    },
                    {
                        "name": "filter[kind][in]",
                        "in": "query",
                        "description": "What the item is. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "string",
                                "enum": [
                                    "document",
                                    "pdf",
                                    "image",
                                    "audio",
                                    "video",
                                    "office",
                                    "other"
                                ],
                                "x-extensible-enum": true
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[owner_id]",
                        "in": "query",
                        "description": "Id of the record whose drive this is, within the `owner_type` given. Must be sent together with `owner_type`. For `organization` it is your own organisation's id.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        },
                        "x-plnnrly-constraint": "Send owner_id and owner_type together."
                    },
                    {
                        "name": "filter[owner_id][in]",
                        "in": "query",
                        "description": "Id of the record whose drive this is, within the `owner_type` given. Must be sent together with `owner_type`. For `organization` it is your own organisation's id. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false,
                        "x-plnnrly-constraint": "Send owner_id and owner_type together."
                    },
                    {
                        "name": "filter[owner_type]",
                        "in": "query",
                        "description": "Which drive the item lives in: `project`, `customer`, `organization` or `user`. Must be sent together with `owner_id`.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "x-extensible-enum": true
                        },
                        "x-plnnrly-constraint": "Send owner_type and owner_id together."
                    },
                    {
                        "name": "filter[source]",
                        "in": "query",
                        "description": "Where the item came from.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "builtin",
                                "upload"
                            ],
                            "x-extensible-enum": true
                        }
                    },
                    {
                        "name": "filter[source][ne]",
                        "in": "query",
                        "description": "Where the item came from.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "builtin",
                                "upload"
                            ],
                            "x-extensible-enum": true
                        }
                    },
                    {
                        "name": "filter[source][in]",
                        "in": "query",
                        "description": "Where the item came from. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "string",
                                "enum": [
                                    "builtin",
                                    "upload"
                                ],
                                "x-extensible-enum": true
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[updated_at]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][gte]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][lt]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[work_items.id]",
                        "in": "query",
                        "description": "The drive items linked to one work item, by its numeric id. A work item's `links.drive_items` points here: it lists every linked item you may open, where `include=drive_items` stops at 25. Using it also requires `project_management::manage_work_items`.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        },
                        "x-plnnrly-requires": {
                            "permission": "project_management::view_backlog",
                            "abilities": [
                                "api:read:project_management"
                            ]
                        }
                    },
                    {
                        "$ref": "#/components/parameters/DriveItemSort"
                    },
                    {
                        "name": "q",
                        "in": "query",
                        "description": "Free-text search over `title`. At most one substring match per request: `q` together with a `contains` filter is 400 invalid-query-parameter.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "minLength": 1
                        }
                    },
                    {
                        "$ref": "#/components/parameters/DriveItemInclude"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "A page of records.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record.",
                                "schema": {
                                    "type": "string",
                                    "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/DriveItemList"
                                            }
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/DriveItemsIndexMeta"
                                        },
                                        "links": {
                                            "$ref": "#/components/schemas/PageLinks"
                                        }
                                    },
                                    "required": [
                                        "data",
                                        "meta",
                                        "links"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "One project's drive root, with owners expanded",
                        "query": {
                            "filter[owner_type]": "project",
                            "filter[owner_id]": 1,
                            "filter[folder_id][null]": "true",
                            "include": "owner,tags",
                            "with_total": 1
                        }
                    }
                ],
                "x-plnnrly-permission": "project_drive::access",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1,
                    "view_detail_total": 3,
                    "surcharges": {
                        "with_total": 2,
                        "include": 1,
                        "include_to_many": 3,
                        "q": 2
                    }
                },
                "x-plnnrly-token-ability": [
                    "api:read:project_drive",
                    "api:read:customer_drive",
                    "api:read:organization_drive",
                    "api:read:user_drive"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/DriveItemList",
                    "detail": "#/components/schemas/DriveItem"
                }
            },
            "post": {
                "operationId": "api.v2.drive-items.store",
                "summary": "Create a drive item",
                "description": "Creates a bodyless document item under a project, a customer, the organisation drive or the caller's own drive, and returns it with a `Location` header. The drive is named by the `owner_type`/`owner_id` pair (one of the few flat creates that accepts a type and id pair), and a record the caller cannot read is refused exactly as a nonexistent one is. For `owner_type=organization` there is no `owner_id` to send: it is always your own organisation. Give the item a body afterwards by POSTing to `/api/v2/drive-documents`; this endpoint never accepts one, and it cannot upload a file. A `folder_id` must belong to the same owner and be a folder you can edit. The declared permission is `project_drive::create`; the other three drive prefixes apply by owner type, and the record rule is what actually decides.",
                "tags": [
                    "Drive"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:project_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:write:customer_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:write:organization_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:write:user_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:project_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:customer_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:organization_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:user_drive"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "owner_type": {
                                        "type": "string",
                                        "enum": [
                                            "project",
                                            "customer",
                                            "organization",
                                            "user"
                                        ],
                                        "description": "The project, customer, organisation or user whose drive this item lives in. An organisation-owned item renders as a bare {\"id\": N} reference."
                                    },
                                    "owner_id": {
                                        "type": "integer",
                                        "description": "The project, customer, organisation or user whose drive this item lives in. An organisation-owned item renders as a bare {\"id\": N} reference."
                                    },
                                    "title": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "description": "Item title."
                                    },
                                    "group": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 255,
                                        "description": "Free-text grouping label shown in the drive tree."
                                    },
                                    "folder_id": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "x-plnnrly-references": {
                                            "resource": "DriveFolder",
                                            "field": "id"
                                        },
                                        "description": "Folder this item sits in, or `null` at the drive root."
                                    },
                                    "tag_ids": {
                                        "type": "array",
                                        "items": {
                                            "type": "integer",
                                            "x-plnnrly-references": {
                                                "resource": "Tag",
                                                "field": "id"
                                            }
                                        },
                                        "maxItems": 50,
                                        "description": "Tags on this item. An item takes at most 50 tags, so the include lists them all. `tag_ids` replaces the whole set."
                                    }
                                },
                                "required": [
                                    "owner_type",
                                    "owner_id",
                                    "title"
                                ],
                                "additionalProperties": false
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Created.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "Idempotency-Replayed": {
                                "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise.",
                                "schema": {
                                    "type": "boolean",
                                    "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise."
                                }
                            },
                            "Location": {
                                "description": "The absolute URL of the created or queued resource.",
                                "schema": {
                                    "type": "string",
                                    "description": "The absolute URL of the created or queued resource."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/DriveItem"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "description": "Conflict.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "oneOf": [
                                        {
                                            "$ref": "#/components/schemas/IdempotencyKeyInFlightProblem"
                                        },
                                        {
                                            "$ref": "#/components/schemas/IdempotencyKeyReusedProblem"
                                        },
                                        {
                                            "$ref": "#/components/schemas/StateConflictProblem"
                                        }
                                    ],
                                    "discriminator": {
                                        "propertyName": "type",
                                        "mapping": {
                                            "https://developers.plnnrly.com/api/v2/problems/idempotency-key-in-flight": "#/components/schemas/IdempotencyKeyInFlightProblem",
                                            "https://developers.plnnrly.com/api/v2/problems/idempotency-key-reused": "#/components/schemas/IdempotencyKeyReusedProblem",
                                            "https://developers.plnnrly.com/api/v2/problems/state-conflict": "#/components/schemas/StateConflictProblem"
                                        }
                                    }
                                },
                                "examples": {
                                    "idempotency-key-in-flight": {
                                        "$ref": "#/components/examples/IdempotencyKeyInFlightProblem"
                                    },
                                    "idempotency-key-reused": {
                                        "$ref": "#/components/examples/IdempotencyKeyReusedProblem"
                                    },
                                    "state-conflict": {
                                        "$ref": "#/components/examples/StateConflictProblem"
                                    }
                                }
                            }
                        }
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "A document in a project's drive",
                        "body": {
                            "owner_type": "project",
                            "owner_id": 1,
                            "title": "Kick-off notes"
                        }
                    }
                ],
                "x-plnnrly-permission": "project_drive::create",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:project_drive",
                    "api:write:customer_drive",
                    "api:write:organization_drive",
                    "api:write:user_drive"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/DriveItemList",
                    "detail": "#/components/schemas/DriveItem"
                }
            },
            "x-plnnrly-allow": [
                "GET",
                "POST"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/drive-items/{drive_item}": {
            "get": {
                "operationId": "api.v2.drive-items.show",
                "summary": "Read a drive item",
                "description": "Returns one drive item in the detail view. `access` is the calling user's own effective access level on it. The body is not here: read `content_record_id` and `GET /api/v2/drive-documents/{drive_document}` for it. `owner`, `drive_folder` and `tags` render as references until named in include; an organisation-owned item's owner is always a bare reference. An archived item is `404` unless `trashed=with` is passed, and `trashed=only` 404s a live one. The declared permission is `project_drive::access`; the other three drive prefixes apply by owner type, and the per-item rule is what actually decides.",
                "tags": [
                    "Drive"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:project_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:read:customer_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:read:organization_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:read:user_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:project_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:customer_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:organization_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:user_drive"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "drive_item",
                        "in": "path",
                        "description": "The DriveItem's numeric id. Accepts a soft-deleted record.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/Trashed"
                    },
                    {
                        "$ref": "#/components/parameters/Meta"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/DriveItem"
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/DriveItemsShowMeta"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "One item with its owner and tags expanded",
                        "path": {
                            "drive_item": 1
                        },
                        "query": {
                            "include": "owner,drive_folder,tags"
                        }
                    }
                ],
                "x-plnnrly-permission": "project_drive::access",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:read:project_drive",
                    "api:read:customer_drive",
                    "api:read:organization_drive",
                    "api:read:user_drive"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/DriveItemList",
                    "detail": "#/components/schemas/DriveItem"
                }
            },
            "patch": {
                "operationId": "api.v2.drive-items.update",
                "summary": "Update a drive item",
                "description": "Partially updates a drive item: absent keys are untouched, an empty body is rejected. `title`, `group`, `folder_id` and `tag_ids` are the writable keys; `tag_ids` replaces the whole tag set. Moving an item to another folder within the same drive is `folder_id`: the folder must belong to the same owner and be one you can edit. Moving an item to a different drive is not possible in v2.0: neither half of the owner pair is accepted on a PATCH, so plnnrly's move-between-drives action has no API equivalent. The body is not writable here either (`PATCH /api/v2/drive-documents/{drive_document}` is the one route that writes a document body), and neither are kind, source, `order_column`, the `content_record` pair, `general_access` or `inherits_permissions`. Send `If-Match` with the `ETag` from your last read to detect a concurrent edit, which answers `412`. Treat it as best-effort only: this resource carries no concurrency token and `updated_at` is second-precision, so two writes inside the same second are indistinguishable. The declared permission is `project_drive::create` (editing is part of the create permission), and it is a superset of what is checked: the record rule needs the owner type's `{owner}_drive::access` plus edit access on the item, which is exactly what plnnrly enforces.",
                "tags": [
                    "Drive"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:project_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:write:customer_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:write:organization_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:write:user_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:project_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:customer_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:organization_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:user_drive"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "drive_item",
                        "in": "path",
                        "description": "The DriveItem's numeric id. Accepts a soft-deleted record.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/Trashed"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "title": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "description": "Item title."
                                    },
                                    "group": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 255,
                                        "description": "Free-text grouping label shown in the drive tree."
                                    },
                                    "folder_id": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "x-plnnrly-references": {
                                            "resource": "DriveFolder",
                                            "field": "id"
                                        },
                                        "description": "Folder this item sits in, or `null` at the drive root."
                                    },
                                    "tag_ids": {
                                        "type": "array",
                                        "items": {
                                            "type": "integer",
                                            "x-plnnrly-references": {
                                                "resource": "Tag",
                                                "field": "id"
                                            }
                                        },
                                        "maxItems": 50,
                                        "description": "Tags on this item. An item takes at most 50 tags, so the include lists them all. `tag_ids` replaces the whole set."
                                    }
                                },
                                "additionalProperties": false,
                                "minProperties": 1
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The updated record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/DriveItem"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Rename an item and replace its tags",
                        "path": {
                            "drive_item": 1
                        },
                        "body": {
                            "title": "Kick-off notes (final)",
                            "tag_ids": []
                        },
                        "headers": {
                            "If-Match": "*"
                        }
                    }
                ],
                "x-plnnrly-permission": "project_drive::create",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:project_drive",
                    "api:write:customer_drive",
                    "api:write:organization_drive",
                    "api:write:user_drive"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/DriveItemList",
                    "detail": "#/components/schemas/DriveItem"
                }
            },
            "delete": {
                "operationId": "api.v2.drive-items.destroy",
                "summary": "Archive a drive item",
                "description": "Archives a drive item. This is plnnrly's Archive action, and it is reversible with `POST /api/v2/drive-items/{drive_item}/actions/restore`. It is a soft delete: the item keeps its body, its comments and its files, and who archived it is recorded in `archived_by`. There is no permanent delete through the API, so `force` is not an accepted parameter. Archiving an already-archived item answers `204` as well, so a retry after a timeout is safe. The declared permission is `project_drive::create`, the same permission as editing and a superset of what the record rule checks: `{owner}_drive::access` plus edit access on the item.",
                "tags": [
                    "Drive"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:destroy:project_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:destroy:customer_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:destroy:organization_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:destroy:user_drive"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "drive_item",
                        "in": "path",
                        "description": "The DriveItem's numeric id. Accepts a soft-deleted record.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "responses": {
                    "204": {
                        "description": "Deleted. No body.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Archive an item",
                        "path": {
                            "drive_item": 1
                        }
                    }
                ],
                "x-plnnrly-permission": "project_drive::create",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:destroy:project_drive",
                    "api:destroy:customer_drive",
                    "api:destroy:organization_drive",
                    "api:destroy:user_drive"
                ]
            },
            "x-plnnrly-allow": [
                "GET",
                "PATCH",
                "DELETE"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/drive-items/{drive_item}/actions/restore": {
            "post": {
                "operationId": "api.v2.drive-items.actions.restore",
                "summary": "Restore an archived drive item",
                "description": "Clears the archive marker, like the restore action on the archived documents list in plnnrly. Restoring requires the owner type's `{owner}_drive::access` plus edit access on the archived item; the declared `project_drive::create` is a superset of that. Restoring an item that is not archived succeeds and returns an `already_in_target_state` warning. The restore finds an archived item without the trashed parameter and does not accept it.",
                "tags": [
                    "Drive"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:project_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:write:customer_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:write:organization_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:write:user_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:project_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:customer_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:organization_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:user_drive"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "drive_item",
                        "in": "path",
                        "description": "The DriveItem's numeric id. Accepts a soft-deleted record.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The record after the action ran.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "Idempotency-Replayed": {
                                "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise.",
                                "schema": {
                                    "type": "boolean",
                                    "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/DriveItem"
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/DriveItemsActionsRestoreMeta"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "$ref": "#/components/responses/Problem409Idempotency"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Restore an archived item",
                        "path": {
                            "drive_item": 1
                        },
                        "body": {}
                    }
                ],
                "x-plnnrly-permission": "project_drive::create",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:project_drive",
                    "api:write:customer_drive",
                    "api:write:organization_drive",
                    "api:write:user_drive"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/DriveItemList",
                    "detail": "#/components/schemas/DriveItem"
                }
            },
            "x-plnnrly-allow": [
                "POST"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/drive-items/{drive_item}/comments": {
            "get": {
                "operationId": "api.v2.drive-items.comments.index",
                "summary": "List a drive item's comments",
                "description": "Returns the comment thread on one drive item (the same thread the drive document editor writes), newest first, keyset-paginated. There is no organisation-wide collection of comments, so this nested path is the only way to list them and `filter[commentable_type]` / `filter[commentable_id]` do not exist: the parent is the path segment. Access to the parent is decided per item (the permission for the owner type, the access inherited from its folder, then any per-item grant), and an archived drive item answers `404`, because comments are hard-deleted and `trashed` is therefore not an accepted parameter on this path. The declared permission is `project_drive::access`; `customer_drive`, `organization_drive` and `user_drive` apply by owner type, and a token covering any of the four is accepted. Pass `include=author` to expand each comment's author; the body is in both views. One comment is read, edited and deleted at its flat path `/comments/{comment}`, listed under Work items.",
                "tags": [
                    "Drive"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:project_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:read:customer_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:read:organization_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:read:user_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:project_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:customer_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:organization_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:user_drive"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "drive_item",
                        "in": "path",
                        "description": "The DriveItem's numeric id. Accepts a soft-deleted record.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/PerPage"
                    },
                    {
                        "$ref": "#/components/parameters/Cursor"
                    },
                    {
                        "$ref": "#/components/parameters/ViewSummary"
                    },
                    {
                        "$ref": "#/components/parameters/Trashed"
                    },
                    {
                        "$ref": "#/components/parameters/WithTotal"
                    },
                    {
                        "name": "filter[author_id]",
                        "in": "query",
                        "description": "Id of the comment's author. Send together with `author_type`.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        },
                        "x-plnnrly-constraint": "Send author_id and author_type together."
                    },
                    {
                        "name": "filter[author_type]",
                        "in": "query",
                        "description": "Type of the comment's author. `user` is the only value in v2.0. Send together with `author_id`.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "x-extensible-enum": true
                        },
                        "x-plnnrly-constraint": "Send author_type and author_id together."
                    },
                    {
                        "name": "filter[created_at]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][gte]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][lt]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][gte]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][lt]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/CommentSort"
                    },
                    {
                        "$ref": "#/components/parameters/CommentInclude"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "A page of records.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record.",
                                "schema": {
                                    "type": "string",
                                    "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/CommentList"
                                            }
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/DriveItemsCommentsIndexMeta"
                                        },
                                        "links": {
                                            "$ref": "#/components/schemas/PageLinks"
                                        }
                                    },
                                    "required": [
                                        "data",
                                        "meta",
                                        "links"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "One drive document's thread with its authors",
                        "path": {
                            "drive_item": 1
                        },
                        "query": {
                            "include": "author",
                            "per_page": 25
                        }
                    }
                ],
                "x-plnnrly-permission": "project_drive::access",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1,
                    "view_detail_total": 3,
                    "surcharges": {
                        "with_total": 2,
                        "include": 1
                    }
                },
                "x-plnnrly-token-ability": [
                    "api:read:project_drive",
                    "api:read:customer_drive",
                    "api:read:organization_drive",
                    "api:read:user_drive"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/CommentList",
                    "detail": "#/components/schemas/Comment"
                }
            },
            "post": {
                "operationId": "api.v2.drive-items.comments.store",
                "summary": "Post a comment on a drive item",
                "description": "Creates one comment on the drive item named in the path, authored as the authenticated user, and returns it in the detail view with a `Location` header pointing at its flat path `/comments/{comment}`, where it is read, edited and deleted. `body` is the only accepted key: the drive item comes from the path, so `commentable_type` and `commentable_id` in the body are a `422`, and the author is the caller, so `author_type` and `author_id` are never accepted. Commenting is a reader's right on a drive item (the same rule the document editor's comment box applies), so the item's access only has to grant view, not edit. Posting works exactly like posting in the editor, so mention notifications, thread events and the author's auto-subscription all fire. The body is rich text: mention spans read from an existing comment may be posted back verbatim and are preserved, but constructing a new mention is not supported in v2.0. An archived drive item is `404`.",
                "tags": [
                    "Drive"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:project_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:write:customer_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:write:organization_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:write:user_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:project_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:customer_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:organization_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:user_drive"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "drive_item",
                        "in": "path",
                        "description": "The DriveItem's numeric id. Accepts a soft-deleted record.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "body": {
                                        "type": "string",
                                        "maxLength": 65535,
                                        "description": "Comment body as rich text. HTML from the plnnrly editor, sanitised on the way out, so what you read back can differ from the bytes you sent. Mention spans (`<span data-type=\"mention\" data-id=\"3\">`) survive untouched and may be posted back verbatim; constructing a new mention is not supported in v2.0."
                                    }
                                },
                                "required": [
                                    "body"
                                ],
                                "additionalProperties": false
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Created.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "Idempotency-Replayed": {
                                "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise.",
                                "schema": {
                                    "type": "boolean",
                                    "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise."
                                }
                            },
                            "Location": {
                                "description": "The absolute URL of the created or queued resource.",
                                "schema": {
                                    "type": "string",
                                    "description": "The absolute URL of the created or queued resource."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Comment"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "$ref": "#/components/responses/Problem409Idempotency"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "A comment on a drive document",
                        "path": {
                            "drive_item": 1
                        },
                        "body": {
                            "body": "<p>Section 3 needs the new pricing table.</p>"
                        }
                    }
                ],
                "x-plnnrly-permission": "project_drive::create",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:project_drive",
                    "api:write:customer_drive",
                    "api:write:organization_drive",
                    "api:write:user_drive"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/CommentList",
                    "detail": "#/components/schemas/Comment"
                }
            },
            "x-plnnrly-allow": [
                "GET",
                "POST"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/drive-items/{drive_item}/files": {
            "get": {
                "operationId": "api.v2.drive-items.files.index",
                "summary": "List a drive item's files",
                "description": "Returns the files on one drive item, newest upload first, keyset-paginated: all three of its file collections (`attachments`, `content` for images pasted into the editor, and `file` for the single uploaded payload), so narrow with `filter[collection]=file` to get the payload alone. There is no organisation-wide collection of files, so this nested path is the only way to list them and `filter[model_type]` / `filter[model_id]` do not exist: the parent is the path segment. Access to the parent is decided per item (the permission for the owner type, the access inherited from its folder, then any per-item grant), and an archived drive item answers `404`. The declared permission is `project_drive::access`; `customer_drive`, `organization_drive` and `user_drive` apply by owner type, and a token covering any of the four is accepted. Listing additionally requires `project_management::manage_work_items` for the files themselves. The API never streams bytes: pass `include=url` for a signed download URL per file, and attach a new file with the two-step upload flow that starts at `POST drive-items/{drive_item}/uploads`.",
                "tags": [
                    "Drive"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:project_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:read:customer_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:read:organization_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:read:user_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:project_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:customer_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:organization_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:user_drive"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "drive_item",
                        "in": "path",
                        "description": "The DriveItem's numeric id. Accepts a soft-deleted record.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/PerPage"
                    },
                    {
                        "$ref": "#/components/parameters/Cursor"
                    },
                    {
                        "$ref": "#/components/parameters/PageNumber"
                    },
                    {
                        "$ref": "#/components/parameters/ViewSummary"
                    },
                    {
                        "$ref": "#/components/parameters/Trashed"
                    },
                    {
                        "name": "filter[collection]",
                        "in": "query",
                        "description": "Narrow to one media collection. A work item registers only `attachments`; a drive item registers `attachments`, `content` (inline editor images) and `file` (the single uploaded payload), so `filter[collection]=file` is how a client asks a drive item for its payload alone.",
                        "required": false,
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "filter[collection][starts]",
                        "in": "query",
                        "description": "Narrow to one media collection. A work item registers only `attachments`; a drive item registers `attachments`, `content` (inline editor images) and `file` (the single uploaded payload), so `filter[collection]=file` is how a client asks a drive item for its payload alone. One value, matched literally as a prefix: send commas and backslashes as they are, and `%` and `_` match themselves rather than acting as wildcards.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "minLength": 1
                        }
                    },
                    {
                        "name": "filter[created_at]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][gte]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][lt]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][gte]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][lt]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/MediaSort"
                    },
                    {
                        "name": "q",
                        "in": "query",
                        "description": "Free-text search over `name`. At most one substring match per request: `q` together with a `contains` filter is 400 invalid-query-parameter.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "minLength": 1
                        }
                    },
                    {
                        "$ref": "#/components/parameters/MediaInclude"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "A page of records.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record.",
                                "schema": {
                                    "type": "string",
                                    "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/MediaList"
                                            }
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/DriveItemsFilesIndexMeta"
                                        },
                                        "links": {
                                            "$ref": "#/components/schemas/PageLinks"
                                        }
                                    },
                                    "required": [
                                        "data",
                                        "meta",
                                        "links"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "One drive item's uploaded payload, with a download URL",
                        "path": {
                            "drive_item": 1
                        },
                        "query": {
                            "filter[collection]": "file",
                            "include": "url",
                            "per_page": 25
                        }
                    }
                ],
                "x-plnnrly-permission": "project_drive::access",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1,
                    "view_detail_total": 3,
                    "surcharges": {
                        "include": 1,
                        "q": 2
                    }
                },
                "x-plnnrly-token-ability": [
                    "api:read:project_drive",
                    "api:read:customer_drive",
                    "api:read:organization_drive",
                    "api:read:user_drive"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/MediaList",
                    "detail": "#/components/schemas/Media"
                }
            },
            "post": {
                "operationId": "api.v2.drive-items.files.store",
                "summary": "Attach an uploaded file to a drive item",
                "description": "Step 3 of 3. Turns bytes you have already uploaded into a file on the drive item named in the path, and returns it in the detail view. `upload_id` is the only key this accepts: create a ticket with `POST drive-items/{drive_item}/uploads`, PUT the raw bytes to its `upload_url`, then send its id here. Everything about the file (its name, size, media type, collection and storage location) is read off the ticket and off the bytes that actually arrived, so `file_name`, `mime_type`, `byte_size`, collection and disk are all a `422`. The file lands in the document's `attachments` collection; `content` belongs to the editor and `file` is the document's own payload, so neither can be written here. The ticket must have been created for this drive item, by this API token, and must not have expired or been registered already. The declared permission is `project_drive::create`; `customer_drive`, `organization_drive` and `user_drive` apply by owner type, and the record rule requires edit access: read access to a document is not enough to attach to it, which is deliberately stricter than plnnrly. Attaching additionally requires `project_management::manage_work_items` for the file itself. An archived document is `404`. `Idempotency-Key` is required: a lost 201 retried without one would report a failure for a create that succeeded. There is no `Location` header (a file has no item URL of its own), and no PATCH or DELETE on a file; remove one in plnnrly.",
                "tags": [
                    "Drive"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:project_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:write:customer_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:write:organization_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:write:user_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:project_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:customer_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:organization_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:user_drive"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "drive_item",
                        "in": "path",
                        "description": "The DriveItem's numeric id. Accepts a soft-deleted record.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKeyRequired"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "upload_id": {
                                        "type": "integer",
                                        "x-plnnrly-references": {
                                            "resource": "Upload",
                                            "field": "id"
                                        },
                                        "description": "The id of the upload ticket whose bytes become this file. Create one with `POST work-items/{work_item}/uploads` or `POST drive-items/{drive_item}/uploads`, PUT the bytes to its `upload_url`, then send its id here. Write-only: it is never rendered on a media row, and it is the only key either attachment store accepts.",
                                        "writeOnly": true
                                    }
                                },
                                "required": [
                                    "upload_id"
                                ],
                                "additionalProperties": false
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Created.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "Idempotency-Replayed": {
                                "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise.",
                                "schema": {
                                    "type": "boolean",
                                    "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Media"
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/DriveItemsFilesStoreMeta"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "description": "Conflict.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "oneOf": [
                                        {
                                            "$ref": "#/components/schemas/IdempotencyKeyInFlightProblem"
                                        },
                                        {
                                            "$ref": "#/components/schemas/IdempotencyKeyReusedProblem"
                                        },
                                        {
                                            "$ref": "#/components/schemas/StateConflictProblem"
                                        }
                                    ],
                                    "discriminator": {
                                        "propertyName": "type",
                                        "mapping": {
                                            "https://developers.plnnrly.com/api/v2/problems/idempotency-key-in-flight": "#/components/schemas/IdempotencyKeyInFlightProblem",
                                            "https://developers.plnnrly.com/api/v2/problems/idempotency-key-reused": "#/components/schemas/IdempotencyKeyReusedProblem",
                                            "https://developers.plnnrly.com/api/v2/problems/state-conflict": "#/components/schemas/StateConflictProblem"
                                        }
                                    }
                                },
                                "examples": {
                                    "idempotency-key-in-flight": {
                                        "$ref": "#/components/examples/IdempotencyKeyInFlightProblem"
                                    },
                                    "idempotency-key-reused": {
                                        "$ref": "#/components/examples/IdempotencyKeyReusedProblem"
                                    },
                                    "state-conflict": {
                                        "$ref": "#/components/examples/StateConflictProblem"
                                    }
                                }
                            }
                        }
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Register staged bytes as a document attachment",
                        "path": {
                            "drive_item": 1
                        },
                        "body": {
                            "upload_id": 1
                        }
                    }
                ],
                "x-plnnrly-permission": "project_drive::create",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:project_drive",
                    "api:write:customer_drive",
                    "api:write:organization_drive",
                    "api:write:user_drive"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/MediaList",
                    "detail": "#/components/schemas/Media"
                }
            },
            "x-plnnrly-allow": [
                "GET",
                "POST"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/drive-items/{drive_item}/uploads": {
            "post": {
                "operationId": "api.v2.drive-items.uploads.store",
                "summary": "Start a file upload on a drive item",
                "description": "Step 1 of 3. Returns an upload ticket whose `upload_url` you PUT the raw bytes to, after which you POST the ticket's id as `upload_id` to `drive-items/{drive_item}/files` to turn those bytes into a file. The bytes never travel through this API: `/api/v2` accepts `application/json` and nothing else, so the upload target is a separate signed route. The file always lands in the document's `attachments` collection (`content` belongs to the editor and `file` is the document's own single payload, so neither is writable here), and the collection, the storage location and the ticket lifetime are all chosen by the server. The declared size is not a bound: the server counts what arrives and refuses anything over 10 MB. The declared permission is `project_drive::create`; `customer_drive`, `organization_drive` and `user_drive` apply by owner type, and the record rule requires edit access: read access to a document is not enough to attach to it, which is deliberately stricter than plnnrly. An archived document is `404`. The ticket is yours alone, is single-use, expires in 30 minutes, and can be deleted with `DELETE uploads/{upload}` while unregistered.",
                "tags": [
                    "Drive"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:project_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:write:customer_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:write:organization_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:write:user_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:project_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:customer_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:organization_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:user_drive"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "drive_item",
                        "in": "path",
                        "description": "The DriveItem's numeric id. Accepts a soft-deleted record.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "file_name": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "description": "The sanitized file name the file will carry. Sent when the ticket is created; control characters are stripped and `#`, `/`, `\\` and spaces become hyphens, so what you read back can differ from what you sent."
                                    },
                                    "byte_size": {
                                        "type": "integer",
                                        "minimum": 1,
                                        "maximum": 10485760,
                                        "description": "The size you declared when creating the ticket. A declaration, not a bound: the server counts the bytes it actually receives."
                                    },
                                    "checksum_sha256": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "pattern": "^[0-9a-f]{64}$",
                                        "minLength": 64,
                                        "maxLength": 64,
                                        "description": "The optional SHA-256 you declared when creating the ticket. When present the server refuses bytes that do not match it."
                                    }
                                },
                                "required": [
                                    "file_name",
                                    "byte_size"
                                ],
                                "additionalProperties": false
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Created.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "Idempotency-Replayed": {
                                "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise.",
                                "schema": {
                                    "type": "boolean",
                                    "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise."
                                }
                            },
                            "Location": {
                                "description": "The absolute URL of the created or queued resource.",
                                "schema": {
                                    "type": "string",
                                    "description": "The absolute URL of the created or queued resource."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Upload"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "description": "Conflict.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "oneOf": [
                                        {
                                            "$ref": "#/components/schemas/IdempotencyKeyInFlightProblem"
                                        },
                                        {
                                            "$ref": "#/components/schemas/IdempotencyKeyReusedProblem"
                                        },
                                        {
                                            "$ref": "#/components/schemas/StateConflictProblem"
                                        }
                                    ],
                                    "discriminator": {
                                        "propertyName": "type",
                                        "mapping": {
                                            "https://developers.plnnrly.com/api/v2/problems/idempotency-key-in-flight": "#/components/schemas/IdempotencyKeyInFlightProblem",
                                            "https://developers.plnnrly.com/api/v2/problems/idempotency-key-reused": "#/components/schemas/IdempotencyKeyReusedProblem",
                                            "https://developers.plnnrly.com/api/v2/problems/state-conflict": "#/components/schemas/StateConflictProblem"
                                        }
                                    }
                                },
                                "examples": {
                                    "idempotency-key-in-flight": {
                                        "$ref": "#/components/examples/IdempotencyKeyInFlightProblem"
                                    },
                                    "idempotency-key-reused": {
                                        "$ref": "#/components/examples/IdempotencyKeyReusedProblem"
                                    },
                                    "state-conflict": {
                                        "$ref": "#/components/examples/StateConflictProblem"
                                    }
                                }
                            }
                        }
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Stage a report for a drive document",
                        "path": {
                            "drive_item": 1
                        },
                        "body": {
                            "file_name": "quarterly-report.pdf",
                            "byte_size": 184320
                        }
                    }
                ],
                "x-plnnrly-permission": "project_drive::create",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:project_drive",
                    "api:write:customer_drive",
                    "api:write:organization_drive",
                    "api:write:user_drive"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/UploadList",
                    "detail": "#/components/schemas/Upload"
                }
            },
            "x-plnnrly-allow": [
                "POST"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/events": {
            "get": {
                "operationId": "api.v2.events.index",
                "summary": "List events",
                "description": "Returns agenda events, latest `start` first. A caller sees only their own events unless they hold `events::manage_for_others`, and `filter[user_id]` defaults to the calling user even then; pass `filter[user_id]=me,<id>` to widen it, which requires that permission. Fetch one window of the calendar with `filter[start][gte]` and `filter[start][lte]`. A recurring master is one row: its occurrences are generated from `recurrence_frequency`, `recurrence_interval`, `selected_weekdays` and `recurrence_exceptions` and are not returned individually, except where plnnrly has separated one into a row of its own (those rows carry `parent_event_id`). `meta.page.total` is only computed when you ask for it with `with_total=true`. Deleted events are excluded unless you pass `trashed=with` or `trashed=only`.",
                "tags": [
                    "Planning"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:events"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:events"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/PerPage"
                    },
                    {
                        "$ref": "#/components/parameters/Cursor"
                    },
                    {
                        "$ref": "#/components/parameters/ViewSummary"
                    },
                    {
                        "$ref": "#/components/parameters/Trashed"
                    },
                    {
                        "$ref": "#/components/parameters/WithTotal"
                    },
                    {
                        "name": "filter[created_at]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][gte]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][lt]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[end]",
                        "in": "query",
                        "description": "When the event ends.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[end][gte]",
                        "in": "query",
                        "description": "When the event ends.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[end][lte]",
                        "in": "query",
                        "description": "When the event ends.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[is_all_day]",
                        "in": "query",
                        "description": "Whether the event occupies whole days rather than a time range.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[is_recurring]",
                        "in": "query",
                        "description": "Whether the event is the master of a recurring series.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[occurrence_date]",
                        "in": "query",
                        "description": "The series date a separated occurrence replaces.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[occurrence_date][gte]",
                        "in": "query",
                        "description": "The series date a separated occurrence replaces.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[occurrence_date][lte]",
                        "in": "query",
                        "description": "The series date a separated occurrence replaces.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[occurrence_date][null]",
                        "in": "query",
                        "description": "The series date a separated occurrence replaces. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[parent_event_id]",
                        "in": "query",
                        "description": "Master event of a separated occurrence. `null` selects everything that is not a separated occurrence.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[parent_event_id][in]",
                        "in": "query",
                        "description": "Master event of a separated occurrence. `null` selects everything that is not a separated occurrence. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[parent_event_id][null]",
                        "in": "query",
                        "description": "Master event of a separated occurrence. `null` selects everything that is not a separated occurrence. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[recurrence_end]",
                        "in": "query",
                        "description": "Last moment a series may generate an occurrence. `null` selects the open-ended series.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[recurrence_end][gte]",
                        "in": "query",
                        "description": "Last moment a series may generate an occurrence. `null` selects the open-ended series.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[recurrence_end][lte]",
                        "in": "query",
                        "description": "Last moment a series may generate an occurrence. `null` selects the open-ended series.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[recurrence_end][null]",
                        "in": "query",
                        "description": "Last moment a series may generate an occurrence. `null` selects the open-ended series. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[recurrence_frequency]",
                        "in": "query",
                        "description": "The unit a recurring series repeats in.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "day",
                                "week",
                                "month",
                                "year"
                            ],
                            "x-extensible-enum": true
                        }
                    },
                    {
                        "name": "filter[recurrence_frequency][ne]",
                        "in": "query",
                        "description": "The unit a recurring series repeats in.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "day",
                                "week",
                                "month",
                                "year"
                            ],
                            "x-extensible-enum": true
                        }
                    },
                    {
                        "name": "filter[recurrence_frequency][in]",
                        "in": "query",
                        "description": "The unit a recurring series repeats in. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "string",
                                "enum": [
                                    "day",
                                    "week",
                                    "month",
                                    "year"
                                ],
                                "x-extensible-enum": true
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[recurrence_pattern]",
                        "in": "query",
                        "description": "How a monthly or yearly series positions its occurrence within the month.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "day_of_month",
                                "weekday_occurrence"
                            ],
                            "x-extensible-enum": true
                        }
                    },
                    {
                        "name": "filter[recurrence_pattern][ne]",
                        "in": "query",
                        "description": "How a monthly or yearly series positions its occurrence within the month.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "day_of_month",
                                "weekday_occurrence"
                            ],
                            "x-extensible-enum": true
                        }
                    },
                    {
                        "name": "filter[recurrence_pattern][in]",
                        "in": "query",
                        "description": "How a monthly or yearly series positions its occurrence within the month. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "string",
                                "enum": [
                                    "day_of_month",
                                    "weekday_occurrence"
                                ],
                                "x-extensible-enum": true
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[recurrence_start]",
                        "in": "query",
                        "description": "First moment a series may generate an occurrence.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[recurrence_start][gte]",
                        "in": "query",
                        "description": "First moment a series may generate an occurrence.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[recurrence_start][lte]",
                        "in": "query",
                        "description": "First moment a series may generate an occurrence.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[recurrence_start][null]",
                        "in": "query",
                        "description": "First moment a series may generate an occurrence. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[start]",
                        "in": "query",
                        "description": "When the event starts. Combine gte and lte to fetch one window of the agenda.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[start][gte]",
                        "in": "query",
                        "description": "When the event starts. Combine gte and lte to fetch one window of the agenda.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[start][lte]",
                        "in": "query",
                        "description": "When the event starts. Combine gte and lte to fetch one window of the agenda.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[task_id]",
                        "in": "query",
                        "description": "Kanban task the event is planned against. `null` selects the events with no task, which are the ones no time can be logged on.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[task_id][in]",
                        "in": "query",
                        "description": "Kanban task the event is planned against. `null` selects the events with no task, which are the ones no time can be logged on. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[task_id][null]",
                        "in": "query",
                        "description": "Kanban task the event is planned against. `null` selects the events with no task, which are the ones no time can be logged on. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[updated_at]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][gte]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][lt]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[user_id]",
                        "in": "query",
                        "description": "Whose agenda the event is on. Accepts the literal `me`, and defaults to the calling user when omitted. Naming another user requires the `events::manage_for_others` permission.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[user_id][in]",
                        "in": "query",
                        "description": "Whose agenda the event is on. Accepts the literal `me`, and defaults to the calling user when omitted. Naming another user requires the `events::manage_for_others` permission. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "$ref": "#/components/parameters/EventSort"
                    },
                    {
                        "name": "q",
                        "in": "query",
                        "description": "Free-text search over `title`. At most one substring match per request: `q` together with a `contains` filter is 400 invalid-query-parameter.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "minLength": 1
                        }
                    },
                    {
                        "$ref": "#/components/parameters/EventInclude"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "A page of records.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record.",
                                "schema": {
                                    "type": "string",
                                    "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/EventList"
                                            }
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/EventsIndexMeta"
                                        },
                                        "links": {
                                            "$ref": "#/components/schemas/PageLinks"
                                        }
                                    },
                                    "required": [
                                        "data",
                                        "meta",
                                        "links"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "One week of my agenda",
                        "query": {
                            "filter[start][gte]": "2026-03-02T00:00:00Z",
                            "filter[start][lte]": "2026-03-08T23:59:59Z",
                            "sort": "start"
                        }
                    },
                    {
                        "name": "My recurring series, with the owner expanded",
                        "query": {
                            "filter[is_recurring]": "true",
                            "include": "user",
                            "view": "detail"
                        }
                    }
                ],
                "x-plnnrly-permission": "events::manage_self",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1,
                    "view_detail_total": 3,
                    "surcharges": {
                        "with_total": 2,
                        "include": 1,
                        "q": 2
                    }
                },
                "x-plnnrly-token-ability": [
                    "api:read:events"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/EventList",
                    "detail": "#/components/schemas/Event"
                }
            },
            "post": {
                "operationId": "api.v2.events.store",
                "summary": "Create an event",
                "description": "Puts an event on the authenticated user's agenda and returns it with a `Location` header. The event is always the caller's own: `user_id` is not a body key, and there is no way to book an event for a colleague through the API, even with `events::manage_for_others`. `start`, `end`, `recurrence_start` and `recurrence_end` are stored exactly as sent: they are RFC 3339 UTC instants and no timezone conversion is applied, unlike the form in plnnrly, which collects local wall-clock time. Send `is_recurring: true` with a frequency and an interval to create a series master; `recurrence_pattern` only affects a monthly or yearly frequency and `selected_weekdays` only a weekly one; a value that does not apply is stored and ignored. Linking a `task_id` requires the task to be one the caller may see, and only an event with a task can have time logged on it. `parent_event_id` and `occurrence_date` are server-assigned and cannot be sent: separating a single occurrence out of a series is done in plnnrly and has no API equivalent in this version. Creating an event notifies anyone `@`-mentioned in the description and pushes the new event to open agenda tabs.",
                "tags": [
                    "Planning"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:events"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:events"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "title": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "description": "Event title, as shown on the calendar tile."
                                    },
                                    "start": {
                                        "type": "string",
                                        "format": "date-time",
                                        "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$",
                                        "x-plnnrly-constraint": "Must be after -5 years.",
                                        "description": "When the event starts. RFC 3339 UTC."
                                    },
                                    "end": {
                                        "type": "string",
                                        "format": "date-time",
                                        "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$",
                                        "x-plnnrly-constraint": "Must be on or after start. Must be before +5 years.",
                                        "description": "When the event ends. RFC 3339 UTC."
                                    },
                                    "is_all_day": {
                                        "type": "boolean",
                                        "description": "Whether the event occupies whole days rather than a time range. An all-day event is measured against the owner's working week rather than against start and end."
                                    },
                                    "description": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "description": "Free-text description, authored in the rich editor. HTML, sanitised on the way in and on the way out; `@`-mention spans are preserved, and changing them is what notifies the people mentioned."
                                    },
                                    "task_id": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "x-plnnrly-references": {
                                            "resource": "Task",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "kanban",
                                        "description": "Kanban task this event is planned against, or `null` for a plain calendar block. Only an event with a task can have time logged on it."
                                    },
                                    "is_recurring": {
                                        "type": "boolean",
                                        "description": "Whether this row is the master of a recurring series. When `true`, the recurrence fields describe how occurrences are generated."
                                    },
                                    "recurrence_interval": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "minimum": 1,
                                        "description": "How many frequency units pass between occurrences: 2 with a weekly frequency is every other week."
                                    },
                                    "recurrence_frequency": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "enum": [
                                            "day",
                                            "week",
                                            "month",
                                            "year",
                                            null
                                        ],
                                        "x-enum-descriptions": {
                                            "day": "Repeats every `recurrence_interval` days.",
                                            "month": "Repeats every `recurrence_interval` months, positioned by `recurrence_pattern`.",
                                            "week": "Repeats every `recurrence_interval` weeks, on the weekdays named in `selected_weekdays`.",
                                            "year": "Repeats every `recurrence_interval` years, positioned by `recurrence_pattern`."
                                        },
                                        "x-extensible-enum": true,
                                        "description": "The unit the series repeats in."
                                    },
                                    "recurrence_pattern": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "enum": [
                                            "day_of_month",
                                            "weekday_occurrence",
                                            null
                                        ],
                                        "x-enum-descriptions": {
                                            "day_of_month": "Falls on the same day number as `recurrence_start`, such as the 17th of every month.",
                                            "weekday_occurrence": "Falls on the same weekday occurrence as `recurrence_start`, such as the third Tuesday of every month."
                                        },
                                        "x-extensible-enum": true,
                                        "description": "How a monthly or yearly series positions its occurrence within the month. Ignored for a daily or weekly frequency."
                                    },
                                    "recurrence_start": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "format": "date-time",
                                        "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$",
                                        "description": "First moment the series may generate an occurrence."
                                    },
                                    "recurrence_end": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "format": "date-time",
                                        "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$",
                                        "x-plnnrly-constraint": "Must be on or after recurrence_start.",
                                        "description": "Last moment the series may generate an occurrence; `null` for an open-ended series."
                                    },
                                    "selected_weekdays": {
                                        "type": [
                                            "array",
                                            "null"
                                        ],
                                        "items": {
                                            "type": "string",
                                            "enum": [
                                                "monday",
                                                "tuesday",
                                                "wednesday",
                                                "thursday",
                                                "friday",
                                                "saturday",
                                                "sunday"
                                            ],
                                            "x-enum-descriptions": {
                                                "friday": "Friday.",
                                                "monday": "Monday.",
                                                "saturday": "Saturday.",
                                                "sunday": "Sunday.",
                                                "thursday": "Thursday.",
                                                "tuesday": "Tuesday.",
                                                "wednesday": "Wednesday."
                                            },
                                            "x-extensible-enum": true
                                        },
                                        "description": "Weekdays a recurring event falls on. Applies to a weekly frequency; ignored otherwise."
                                    },
                                    "recurrence_exceptions": {
                                        "type": [
                                            "array",
                                            "null"
                                        ],
                                        "items": {
                                            "type": "string",
                                            "format": "date",
                                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                                        },
                                        "description": "Dates on which this recurrence does not occur. plnnrly adds one whenever a single occurrence is separated or deleted."
                                    }
                                },
                                "required": [
                                    "title",
                                    "start",
                                    "end",
                                    "is_all_day"
                                ],
                                "additionalProperties": false
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Created.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "Idempotency-Replayed": {
                                "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise.",
                                "schema": {
                                    "type": "boolean",
                                    "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise."
                                }
                            },
                            "Location": {
                                "description": "The absolute URL of the created or queued resource.",
                                "schema": {
                                    "type": "string",
                                    "description": "The absolute URL of the created or queued resource."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Event"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "$ref": "#/components/responses/Problem409Idempotency"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "A one-off block on my agenda",
                        "body": {
                            "title": "Design review",
                            "start": "2026-03-03T09:00:00Z",
                            "end": "2026-03-03T10:30:00Z",
                            "is_all_day": false
                        }
                    },
                    {
                        "name": "A weekly series, planned against a task",
                        "body": {
                            "title": "Sprint refinement",
                            "start": "2026-03-03T13:00:00Z",
                            "end": "2026-03-03T14:00:00Z",
                            "is_all_day": false,
                            "task_id": 1,
                            "is_recurring": true,
                            "recurrence_frequency": "week",
                            "recurrence_interval": 1,
                            "recurrence_start": "2026-03-03T13:00:00Z",
                            "recurrence_end": "2026-06-30T14:00:00Z",
                            "selected_weekdays": [
                                "tuesday"
                            ]
                        }
                    }
                ],
                "x-plnnrly-permission": "events::manage_self",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:events"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/EventList",
                    "detail": "#/components/schemas/Event"
                }
            },
            "x-plnnrly-allow": [
                "GET",
                "POST"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/events/{event}": {
            "get": {
                "operationId": "api.v2.events.show",
                "summary": "Read an event",
                "description": "Returns one event in the detail view, which is where `description`, `selected_weekdays` and `recurrence_exceptions` live; the summary view a list returns omits those three. `user` renders as a `{\"id\": N}` reference; `?include=user` expands it, and that expansion additionally requires `users::read` and a token scope covering the users prefix. A deleted event is `404` unless you pass `trashed=with`.",
                "tags": [
                    "Planning"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:events"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:events"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "event",
                        "in": "path",
                        "description": "The Event's numeric id. Accepts a soft-deleted record.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/Trashed"
                    },
                    {
                        "$ref": "#/components/parameters/Meta"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Event"
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/EventsShowMeta"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "One event with its owner expanded",
                        "path": {
                            "event": 1
                        },
                        "query": {
                            "include": "user"
                        }
                    }
                ],
                "x-plnnrly-permission": "events::manage_self",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:read:events"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/EventList",
                    "detail": "#/components/schemas/Event"
                }
            },
            "patch": {
                "operationId": "api.v2.events.update",
                "summary": "Update an event",
                "description": "Partially updates an event: absent keys are untouched, an empty body is rejected. ⚠ On a recurring master this edits the whole series (every occurrence moves), where plnnrly would have asked \"this occurrence / this and following / all\". There is no per-occurrence write surface in this version: `parent_event_id` and `occurrence_date` are server-assigned. `start`, `end` and the two recurrence instants are stored exactly as sent, with no timezone conversion. Send `task_id: null` to unlink the task, which also stops time being logged on the event; setting a task additionally requires that the task be selectable by the event's owner, not merely by you, so re-pointing a colleague's event at your own task is refused. Note: `end` is only checked against `start` when both are sent in the same request, so sending `end` alone can leave it before the stored `start`; an event in that state measures zero planned minutes everywhere it is read. Changing the `@`-mentions in the description notifies the people newly mentioned. Send `If-Match` with the `ETag` from your last read to detect a concurrent edit, which answers `412`. Treat it as best-effort only: this resource carries no concurrency token, and `updated_at` is second-precision, so two writes inside the same second are indistinguishable.",
                "tags": [
                    "Planning"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:events"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:events"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "event",
                        "in": "path",
                        "description": "The Event's numeric id. Accepts a soft-deleted record.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/Trashed"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "title": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "description": "Event title, as shown on the calendar tile."
                                    },
                                    "start": {
                                        "type": "string",
                                        "format": "date-time",
                                        "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$",
                                        "x-plnnrly-constraint": "Must be after -5 years.",
                                        "description": "When the event starts. RFC 3339 UTC."
                                    },
                                    "end": {
                                        "type": "string",
                                        "format": "date-time",
                                        "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$",
                                        "x-plnnrly-constraint": "Must be on or after start. Must be before +5 years.",
                                        "description": "When the event ends. RFC 3339 UTC."
                                    },
                                    "is_all_day": {
                                        "type": "boolean",
                                        "description": "Whether the event occupies whole days rather than a time range. An all-day event is measured against the owner's working week rather than against start and end."
                                    },
                                    "description": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "description": "Free-text description, authored in the rich editor. HTML, sanitised on the way in and on the way out; `@`-mention spans are preserved, and changing them is what notifies the people mentioned."
                                    },
                                    "task_id": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "x-plnnrly-references": {
                                            "resource": "Task",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "kanban",
                                        "description": "Kanban task this event is planned against, or `null` for a plain calendar block. Only an event with a task can have time logged on it."
                                    },
                                    "is_recurring": {
                                        "type": "boolean",
                                        "description": "Whether this row is the master of a recurring series. When `true`, the recurrence fields describe how occurrences are generated."
                                    },
                                    "recurrence_interval": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "minimum": 1,
                                        "description": "How many frequency units pass between occurrences: 2 with a weekly frequency is every other week."
                                    },
                                    "recurrence_frequency": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "enum": [
                                            "day",
                                            "week",
                                            "month",
                                            "year",
                                            null
                                        ],
                                        "x-enum-descriptions": {
                                            "day": "Repeats every `recurrence_interval` days.",
                                            "month": "Repeats every `recurrence_interval` months, positioned by `recurrence_pattern`.",
                                            "week": "Repeats every `recurrence_interval` weeks, on the weekdays named in `selected_weekdays`.",
                                            "year": "Repeats every `recurrence_interval` years, positioned by `recurrence_pattern`."
                                        },
                                        "x-extensible-enum": true,
                                        "description": "The unit the series repeats in."
                                    },
                                    "recurrence_pattern": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "enum": [
                                            "day_of_month",
                                            "weekday_occurrence",
                                            null
                                        ],
                                        "x-enum-descriptions": {
                                            "day_of_month": "Falls on the same day number as `recurrence_start`, such as the 17th of every month.",
                                            "weekday_occurrence": "Falls on the same weekday occurrence as `recurrence_start`, such as the third Tuesday of every month."
                                        },
                                        "x-extensible-enum": true,
                                        "description": "How a monthly or yearly series positions its occurrence within the month. Ignored for a daily or weekly frequency."
                                    },
                                    "recurrence_start": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "format": "date-time",
                                        "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$",
                                        "description": "First moment the series may generate an occurrence."
                                    },
                                    "recurrence_end": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "format": "date-time",
                                        "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$",
                                        "x-plnnrly-constraint": "Must be on or after recurrence_start.",
                                        "description": "Last moment the series may generate an occurrence; `null` for an open-ended series."
                                    },
                                    "selected_weekdays": {
                                        "type": [
                                            "array",
                                            "null"
                                        ],
                                        "items": {
                                            "type": "string",
                                            "enum": [
                                                "monday",
                                                "tuesday",
                                                "wednesday",
                                                "thursday",
                                                "friday",
                                                "saturday",
                                                "sunday"
                                            ],
                                            "x-enum-descriptions": {
                                                "friday": "Friday.",
                                                "monday": "Monday.",
                                                "saturday": "Saturday.",
                                                "sunday": "Sunday.",
                                                "thursday": "Thursday.",
                                                "tuesday": "Tuesday.",
                                                "wednesday": "Wednesday."
                                            },
                                            "x-extensible-enum": true
                                        },
                                        "description": "Weekdays a recurring event falls on. Applies to a weekly frequency; ignored otherwise."
                                    },
                                    "recurrence_exceptions": {
                                        "type": [
                                            "array",
                                            "null"
                                        ],
                                        "items": {
                                            "type": "string",
                                            "format": "date",
                                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                                        },
                                        "description": "Dates on which this recurrence does not occur. plnnrly adds one whenever a single occurrence is separated or deleted."
                                    }
                                },
                                "additionalProperties": false,
                                "minProperties": 1
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The updated record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Event"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "description": "Forbidden.\n\n`forbidden-by-policy`: The event is your own and you hold `events::manage_for_others` but not `events::manage_self`, or the `task_id` you send is a task the event's owner cannot select. Another user's event answers `404` instead when you do not hold `events::manage_for_others`, because you cannot see it.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "oneOf": [
                                        {
                                            "$ref": "#/components/schemas/ForbiddenByPolicyProblem"
                                        },
                                        {
                                            "$ref": "#/components/schemas/InsufficientPermissionProblem"
                                        },
                                        {
                                            "$ref": "#/components/schemas/InsufficientTokenScopeProblem"
                                        }
                                    ],
                                    "discriminator": {
                                        "propertyName": "type",
                                        "mapping": {
                                            "https://developers.plnnrly.com/api/v2/problems/forbidden-by-policy": "#/components/schemas/ForbiddenByPolicyProblem",
                                            "https://developers.plnnrly.com/api/v2/problems/insufficient-permission": "#/components/schemas/InsufficientPermissionProblem",
                                            "https://developers.plnnrly.com/api/v2/problems/insufficient-token-scope": "#/components/schemas/InsufficientTokenScopeProblem"
                                        }
                                    }
                                },
                                "examples": {
                                    "forbidden-by-policy": {
                                        "$ref": "#/components/examples/ForbiddenByPolicyProblem"
                                    },
                                    "insufficient-permission": {
                                        "$ref": "#/components/examples/InsufficientPermissionProblem"
                                    },
                                    "insufficient-token-scope": {
                                        "$ref": "#/components/examples/InsufficientTokenScopeProblem"
                                    }
                                }
                            }
                        }
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Move an event an hour later",
                        "path": {
                            "event": 1
                        },
                        "body": {
                            "start": "2026-03-03T10:00:00Z",
                            "end": "2026-03-03T11:30:00Z"
                        },
                        "headers": {
                            "If-Match": "*"
                        }
                    },
                    {
                        "name": "Unlink the task",
                        "path": {
                            "event": 1
                        },
                        "body": {
                            "task_id": null
                        }
                    }
                ],
                "x-plnnrly-permission": "events::manage_self",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:events"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/EventList",
                    "detail": "#/components/schemas/Event"
                }
            },
            "delete": {
                "operationId": "api.v2.events.destroy",
                "summary": "Delete an event",
                "description": "Soft-deletes an event; `POST events/{event}/actions/restore` reverses it. ⚠ On a recurring master this removes the whole series (every occurrence disappears), where plnnrly would have asked \"this occurrence / this and following / all\". There is no `force` parameter: this resource is not force-deletable, so `?force=true` is a `400` `unsupported-parameter`. Deleting an event that is already deleted answers `204` as well, so a retry is safe. Any time already logged against the event's task is unaffected. Known gap: unlike plnnrly, an API delete does not push the removal to open agenda tabs, so a colleague with the calendar open may keep seeing the event until they reload.",
                "tags": [
                    "Planning"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:destroy:events"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "event",
                        "in": "path",
                        "description": "The Event's numeric id. Accepts a soft-deleted record.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "responses": {
                    "204": {
                        "description": "Deleted. No body.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "description": "Forbidden.\n\n`forbidden-by-policy`: The event is your own and you hold `events::manage_for_others` but not `events::manage_self`. Another user's event answers `404` instead when you do not hold `events::manage_for_others`, because you cannot see it.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "oneOf": [
                                        {
                                            "$ref": "#/components/schemas/ForbiddenByPolicyProblem"
                                        },
                                        {
                                            "$ref": "#/components/schemas/InsufficientPermissionProblem"
                                        },
                                        {
                                            "$ref": "#/components/schemas/InsufficientTokenScopeProblem"
                                        }
                                    ],
                                    "discriminator": {
                                        "propertyName": "type",
                                        "mapping": {
                                            "https://developers.plnnrly.com/api/v2/problems/forbidden-by-policy": "#/components/schemas/ForbiddenByPolicyProblem",
                                            "https://developers.plnnrly.com/api/v2/problems/insufficient-permission": "#/components/schemas/InsufficientPermissionProblem",
                                            "https://developers.plnnrly.com/api/v2/problems/insufficient-token-scope": "#/components/schemas/InsufficientTokenScopeProblem"
                                        }
                                    }
                                },
                                "examples": {
                                    "forbidden-by-policy": {
                                        "$ref": "#/components/examples/ForbiddenByPolicyProblem"
                                    },
                                    "insufficient-permission": {
                                        "$ref": "#/components/examples/InsufficientPermissionProblem"
                                    },
                                    "insufficient-token-scope": {
                                        "$ref": "#/components/examples/InsufficientTokenScopeProblem"
                                    }
                                }
                            }
                        }
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Delete an event",
                        "path": {
                            "event": 1
                        }
                    }
                ],
                "x-plnnrly-permission": "events::manage_self",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:destroy:events"
                ]
            },
            "x-plnnrly-allow": [
                "GET",
                "PATCH",
                "DELETE"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/events/{event}/actions/restore": {
            "post": {
                "operationId": "api.v2.events.actions.restore",
                "summary": "Restore a deleted event",
                "description": "Clears the soft-delete marker and returns the event in the detail view, putting it back on its owner's agenda. Restoring somebody else's event additionally requires `events::manage_for_others`, on top of `events::manage_self`. Restoring an event that is not deleted succeeds and returns an `already_in_target_state` warning, so a retry is safe. On a recurring master this brings the whole series back. The restore reaches a deleted event without the trashed parameter and does not accept it.",
                "tags": [
                    "Planning"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:events"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:events"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "event",
                        "in": "path",
                        "description": "The Event's numeric id. Accepts a soft-deleted record.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The record after the action ran.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "Idempotency-Replayed": {
                                "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise.",
                                "schema": {
                                    "type": "boolean",
                                    "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Event"
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/EventsActionsRestoreMeta"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "$ref": "#/components/responses/Problem409Idempotency"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Restore a deleted event",
                        "path": {
                            "event": 1
                        },
                        "body": {}
                    }
                ],
                "x-plnnrly-permission": "events::manage_self",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:events"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/EventList",
                    "detail": "#/components/schemas/Event"
                }
            },
            "x-plnnrly-allow": [
                "POST"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/free-days": {
            "get": {
                "operationId": "api.v2.free-days.index",
                "summary": "List free days",
                "description": "Returns the organisation's free days (the calendar dates on which it is closed, whether created from a recurring holiday or added by hand), newest first. Free days are excluded from the working days a furlough request consumes. There is no `year` parameter: filter a year as `filter[date][gte]=2026-01-01&filter[date][lte]=2026-12-31`.",
                "tags": [
                    "Leave"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:free_days"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:free_days"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/PerPage"
                    },
                    {
                        "$ref": "#/components/parameters/Cursor"
                    },
                    {
                        "$ref": "#/components/parameters/PageNumber"
                    },
                    {
                        "$ref": "#/components/parameters/ViewSummary"
                    },
                    {
                        "name": "filter[created_at]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][gte]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][lt]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[date]",
                        "in": "query",
                        "description": "Calendar date the organisation is closed.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[date][gte]",
                        "in": "query",
                        "description": "Calendar date the organisation is closed.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[date][lte]",
                        "in": "query",
                        "description": "Calendar date the organisation is closed.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[holiday_id]",
                        "in": "query",
                        "description": "Recurring holiday the free day was instantiated from. `null` selects the manually added days.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[holiday_id][in]",
                        "in": "query",
                        "description": "Recurring holiday the free day was instantiated from. `null` selects the manually added days. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[holiday_id][null]",
                        "in": "query",
                        "description": "Recurring holiday the free day was instantiated from. `null` selects the manually added days. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[updated_at]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][gte]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][lt]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/FreeDaySort"
                    },
                    {
                        "name": "q",
                        "in": "query",
                        "description": "Free-text search over `description`. At most one substring match per request: `q` together with a `contains` filter is 400 invalid-query-parameter.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "minLength": 1
                        }
                    },
                    {
                        "$ref": "#/components/parameters/FreeDayInclude"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "A page of records.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record.",
                                "schema": {
                                    "type": "string",
                                    "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/FreeDayList"
                                            }
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/FreeDaysIndexMeta"
                                        },
                                        "links": {
                                            "$ref": "#/components/schemas/PageLinks"
                                        }
                                    },
                                    "required": [
                                        "data",
                                        "meta",
                                        "links"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "The free days in 2026, newest first",
                        "query": {
                            "filter[date][gte]": "2026-01-01",
                            "filter[date][lte]": "2026-12-31"
                        }
                    }
                ],
                "x-plnnrly-permission": "free_days::manage",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1,
                    "view_detail_total": 3,
                    "surcharges": {
                        "include": 1,
                        "q": 2
                    }
                },
                "x-plnnrly-token-ability": [
                    "api:read:free_days"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/FreeDayList",
                    "detail": "#/components/schemas/FreeDay"
                }
            },
            "post": {
                "operationId": "api.v2.free-days.store",
                "summary": "Create a free day",
                "description": "Creates one free day and returns it with a `Location` header. `description` is required and is not derived from `holiday_id`: for a holiday-derived day plnnrly copies the holiday name into it at creation time and the two drift independently, so send the label you want. Nothing enforces one free day per date: plnnrly's year generation skips an already-used date, but this endpoint does not, and a duplicate date is a plain `201`. To generate a year of free days from the recurring holidays, call this endpoint once per holiday.",
                "tags": [
                    "Leave"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:free_days"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:free_days"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "description": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "description": "Label for the free day, e.g. `Christmas Day`. For a holiday-derived day this is a copy of the holiday name taken when the free day was created, so renaming the holiday does not rewrite it."
                                    },
                                    "date": {
                                        "type": "string",
                                        "format": "date",
                                        "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                                        "description": "Calendar date the organisation is closed."
                                    },
                                    "holiday_id": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "x-plnnrly-references": {
                                            "resource": "Holiday",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "holidays",
                                        "description": "Recurring holiday this free day instantiates, or `null` for a day added by hand. The foreign key nulls on delete, so removing the holiday leaves the free day behind as a manual day still carrying the copied label."
                                    }
                                },
                                "required": [
                                    "description",
                                    "date"
                                ],
                                "additionalProperties": false
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Created.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "Idempotency-Replayed": {
                                "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise.",
                                "schema": {
                                    "type": "boolean",
                                    "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise."
                                }
                            },
                            "Location": {
                                "description": "The absolute URL of the created or queued resource.",
                                "schema": {
                                    "type": "string",
                                    "description": "The absolute URL of the created or queued resource."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/FreeDay"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "$ref": "#/components/responses/Problem409Idempotency"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "A manually added closure day",
                        "body": {
                            "description": "Office closed for the annual outing",
                            "date": "2026-07-03"
                        }
                    },
                    {
                        "name": "A day instantiated from a recurring holiday",
                        "body": {
                            "holiday_id": 4,
                            "description": "Christmas Day",
                            "date": "2026-12-25"
                        }
                    }
                ],
                "x-plnnrly-permission": "free_days::manage",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:free_days"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/FreeDayList",
                    "detail": "#/components/schemas/FreeDay"
                }
            },
            "x-plnnrly-allow": [
                "GET",
                "POST"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/free-days/{free_day}": {
            "get": {
                "operationId": "api.v2.free-days.show",
                "summary": "Read a free day",
                "description": "Returns one free day. The summary and detail views are identical on this resource: every field is small enough for a list row. `holiday` renders as an `{\"id\": N}` reference; `?include=holiday` expands it, and that expansion additionally requires `holidays::manage` and a token scope covering the holidays prefix.",
                "tags": [
                    "Leave"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:free_days"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:free_days"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "free_day",
                        "in": "path",
                        "description": "The FreeDay's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/FreeDay"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "One free day with its holiday expanded",
                        "path": {
                            "free_day": 1
                        },
                        "query": {
                            "include": "holiday"
                        }
                    }
                ],
                "x-plnnrly-permission": "free_days::manage",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:read:free_days"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/FreeDayList",
                    "detail": "#/components/schemas/FreeDay"
                }
            },
            "patch": {
                "operationId": "api.v2.free-days.update",
                "summary": "Update a free day",
                "description": "Partially updates a free day: absent keys are untouched, an empty body is rejected. Send `holiday_id: null` to detach a free day from its holiday and keep it as a manual day. Send `If-Match` with the `ETag` from your last read to detect a concurrent edit, which answers `412`. Treat it as best-effort only: this resource carries no concurrency token, and `updated_at` is second-precision, so two writes inside the same second are indistinguishable.",
                "tags": [
                    "Leave"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:free_days"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:free_days"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "free_day",
                        "in": "path",
                        "description": "The FreeDay's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "description": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "description": "Label for the free day, e.g. `Christmas Day`. For a holiday-derived day this is a copy of the holiday name taken when the free day was created, so renaming the holiday does not rewrite it."
                                    },
                                    "date": {
                                        "type": "string",
                                        "format": "date",
                                        "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                                        "description": "Calendar date the organisation is closed."
                                    },
                                    "holiday_id": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "x-plnnrly-references": {
                                            "resource": "Holiday",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "holidays",
                                        "description": "Recurring holiday this free day instantiates, or `null` for a day added by hand. The foreign key nulls on delete, so removing the holiday leaves the free day behind as a manual day still carrying the copied label."
                                    }
                                },
                                "additionalProperties": false,
                                "minProperties": 1
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The updated record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/FreeDay"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Detach a free day from its holiday and relabel it",
                        "path": {
                            "free_day": 1
                        },
                        "body": {
                            "holiday_id": null,
                            "description": "Office closed (moved)"
                        },
                        "headers": {
                            "If-Match": "*"
                        }
                    }
                ],
                "x-plnnrly-permission": "free_days::manage",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:free_days"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/FreeDayList",
                    "detail": "#/components/schemas/FreeDay"
                }
            },
            "delete": {
                "operationId": "api.v2.free-days.destroy",
                "summary": "Delete a free day",
                "description": "Permanently deletes a free day. This resource is hard-delete: there is no restore action, no `force` parameter and no `trashed` parameter. Nothing blocks the delete: a free day carries no dependants, and deleting one simply puts the date back into the working days a furlough request consumes.",
                "tags": [
                    "Leave"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:destroy:free_days"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "free_day",
                        "in": "path",
                        "description": "The FreeDay's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "responses": {
                    "204": {
                        "description": "Deleted. No body.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Delete a free day",
                        "path": {
                            "free_day": 1
                        }
                    }
                ],
                "x-plnnrly-permission": "free_days::manage",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:destroy:free_days"
                ]
            },
            "x-plnnrly-allow": [
                "GET",
                "PATCH",
                "DELETE"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/furlough-balance-mutations": {
            "get": {
                "operationId": "api.v2.furlough-balance-mutations.index",
                "summary": "List furlough balance mutations",
                "description": "Returns furlough balance mutations, an employee's own leave history by default, newest first. This one endpoint covers three lists in plnnrly: a year of one employee's history is `filter[user_id]=me&filter[year]=2026`, the manager's approval queue is `filter[is_approved][null]=true`, and a per-employee drill-down is `filter[user_id]=N`. The furlough prefix has no read permission, so what you see depends on your permissions alone: a caller sees only their own mutations unless they hold `furlough::view_all` or `furlough::manage`, and pointing `filter[user_id]` at another employee requires one of those two permissions.",
                "tags": [
                    "Leave"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:furlough"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:furlough"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/PerPage"
                    },
                    {
                        "$ref": "#/components/parameters/Cursor"
                    },
                    {
                        "$ref": "#/components/parameters/ViewSummary"
                    },
                    {
                        "$ref": "#/components/parameters/WithTotal"
                    },
                    {
                        "name": "filter[acceptor_id]",
                        "in": "query",
                        "description": "The user who approved the mutation. `null` selects the mutations no one has approved: the pending ones and the denied ones alike.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[acceptor_id][in]",
                        "in": "query",
                        "description": "The user who approved the mutation. `null` selects the mutations no one has approved: the pending ones and the denied ones alike. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[acceptor_id][null]",
                        "in": "query",
                        "description": "The user who approved the mutation. `null` selects the mutations no one has approved: the pending ones and the denied ones alike. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[created_at]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][gte]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][lt]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[end]",
                        "in": "query",
                        "description": "End of the period the mutation covers, in UTC.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[end][gte]",
                        "in": "query",
                        "description": "End of the period the mutation covers, in UTC.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[end][lte]",
                        "in": "query",
                        "description": "End of the period the mutation covers, in UTC.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[is_approved]",
                        "in": "query",
                        "description": "Approval state. `true` selects the approved mutations, `false` the denied ones, and `filter[is_approved][null]=true` the ones still pending approval, which is the approval queue.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[is_approved][null]",
                        "in": "query",
                        "description": "Approval state. `true` selects the approved mutations, `false` the denied ones, and `filter[is_approved][null]=true` the ones still pending approval, which is the approval queue. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[start]",
                        "in": "query",
                        "description": "Start of the period the mutation covers, in UTC.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[start][gte]",
                        "in": "query",
                        "description": "Start of the period the mutation covers, in UTC.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[start][lte]",
                        "in": "query",
                        "description": "Start of the period the mutation covers, in UTC.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[type]",
                        "in": "query",
                        "description": "Which leave balance the mutation moves.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "regular",
                                "special"
                            ],
                            "x-extensible-enum": true
                        }
                    },
                    {
                        "name": "filter[type][ne]",
                        "in": "query",
                        "description": "Which leave balance the mutation moves.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "regular",
                                "special"
                            ],
                            "x-extensible-enum": true
                        }
                    },
                    {
                        "name": "filter[type][in]",
                        "in": "query",
                        "description": "Which leave balance the mutation moves. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "string",
                                "enum": [
                                    "regular",
                                    "special"
                                ],
                                "x-extensible-enum": true
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[updated_at]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][gte]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][lt]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[user_id]",
                        "in": "query",
                        "description": "Whose mutations to return. Accepts the literal `me`. Naming another user requires the `furlough::view_all` or `furlough::manage` permission, the same two that show the whole organisation's mutations.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[user_id][in]",
                        "in": "query",
                        "description": "Whose mutations to return. Accepts the literal `me`. Naming another user requires the `furlough::view_all` or `furlough::manage` permission, the same two that show the whole organisation's mutations. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[year]",
                        "in": "query",
                        "description": "The balance year the mutation counts against. Every balance sum counts by this year, so it is the right way to narrow a balance history to one year, rather than a range over `start`.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[year][in]",
                        "in": "query",
                        "description": "The balance year the mutation counts against. Every balance sum counts by this year, so it is the right way to narrow a balance history to one year, rather than a range over `start`. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "$ref": "#/components/parameters/FurloughBalanceMutationSort"
                    },
                    {
                        "name": "q",
                        "in": "query",
                        "description": "Free-text search over `description`. At most one substring match per request: `q` together with a `contains` filter is 400 invalid-query-parameter.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "minLength": 1
                        }
                    },
                    {
                        "$ref": "#/components/parameters/FurloughBalanceMutationInclude"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "A page of records.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record.",
                                "schema": {
                                    "type": "string",
                                    "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/FurloughBalanceMutationList"
                                            }
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/FurloughBalanceMutationsIndexMeta"
                                        },
                                        "links": {
                                            "$ref": "#/components/schemas/PageLinks"
                                        }
                                    },
                                    "required": [
                                        "data",
                                        "meta",
                                        "links"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "The pending approval queue across the organisation",
                        "query": {
                            "filter[is_approved][null]": "true",
                            "sort": "start"
                        }
                    },
                    {
                        "name": "My own 2026 leave history",
                        "query": {
                            "filter[user_id]": "me",
                            "filter[year]": "2026"
                        }
                    }
                ],
                "x-plnnrly-permission": null,
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1,
                    "view_detail_total": 3,
                    "surcharges": {
                        "with_total": 2,
                        "include": 1,
                        "q": 2
                    }
                },
                "x-plnnrly-token-ability": [
                    "api:read:furlough"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/FurloughBalanceMutationList",
                    "detail": "#/components/schemas/FurloughBalanceMutation"
                }
            },
            "post": {
                "operationId": "api.v2.furlough-balance-mutations.store",
                "summary": "Create a furlough balance mutation",
                "description": "Creates one mutation for the authenticated user and returns it with a `Location` header. `user_id` is always the caller: this resource carries no on-behalf-of field, so a manager cannot post a mutation for another employee through the API even though the balance screen in plnnrly can. A negative `minutes` is a leave request over the period; a positive `minutes` grants balance and requires `furlough::manage`. `is_approved` is not accepted: the mutation is always the caller's own, so every mutation is created pending and is approved or denied by someone else through PATCH. A special-leave request that would take the special-leave balance for the year below zero is rejected with `422`. Creating a pending mutation notifies every `furlough::manage` holder by e-mail.",
                "tags": [
                    "Leave"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:furlough"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:furlough"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "year": {
                                        "type": "integer",
                                        "minimum": 1970,
                                        "maximum": 2200,
                                        "description": "The balance year this mutation counts against. It is not derived from `start`: every balance sum counts by this year, so a mutation whose period moves into another year keeps counting against the year sent here until `year` is sent too."
                                    },
                                    "type": {
                                        "type": "string",
                                        "enum": [
                                            "regular",
                                            "special"
                                        ],
                                        "x-enum-descriptions": {
                                            "regular": "Ordinary paid leave, drawn from the employee's regular annual balance.",
                                            "special": "Special leave: a separate balance granted for a specific entitlement, never offset against regular leave."
                                        },
                                        "x-extensible-enum": true,
                                        "description": "Which leave balance this mutation moves. Regular and special leave are separate balances and never offset one another."
                                    },
                                    "minutes": {
                                        "type": "integer",
                                        "x-plnnrly-grant-guard": "A positive value grants balance and requires the `furlough::manage` permission; a caller with only `furlough::create_for_yourself` may write a negative value (a leave request) or zero.",
                                        "x-plnnrly-special-leave-guard": "On a `special` mutation, the resulting special-leave balance for the year may not go negative.",
                                        "description": "The signed balance delta in minutes. Negative consumes balance (a leave request); positive grants it (a manager's adjustment, which requires the `furlough::manage` permission to write). The API never computes this from the employee's working week or the organisation's free days: send the exact number of minutes you mean."
                                    },
                                    "start": {
                                        "type": "string",
                                        "format": "date-time",
                                        "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$",
                                        "description": "Start of the period this mutation covers, in UTC. Equal to `end` on a manual balance adjustment, which covers no period at all."
                                    },
                                    "end": {
                                        "type": "string",
                                        "format": "date-time",
                                        "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$",
                                        "x-plnnrly-constraint": "Must be on or after start.",
                                        "description": "End of the period this mutation covers, in UTC. Never before `start`, and equal to it on a manual balance adjustment."
                                    },
                                    "description": {
                                        "type": "string",
                                        "description": "The employee's or manager's own words for this mutation: the reason for the leave, or what the adjustment is for. The only field `?q=` searches."
                                    }
                                },
                                "required": [
                                    "year",
                                    "minutes",
                                    "start",
                                    "end",
                                    "description"
                                ],
                                "additionalProperties": false
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Created.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "Idempotency-Replayed": {
                                "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise.",
                                "schema": {
                                    "type": "boolean",
                                    "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise."
                                }
                            },
                            "Location": {
                                "description": "The absolute URL of the created or queued resource.",
                                "schema": {
                                    "type": "string",
                                    "description": "The absolute URL of the created or queued resource."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/FurloughBalanceMutation"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "$ref": "#/components/responses/Problem409Idempotency"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Request two days of regular leave",
                        "body": {
                            "year": 2026,
                            "minutes": -960,
                            "start": "2026-07-06T00:00:00Z",
                            "end": "2026-07-07T23:59:59Z",
                            "description": "Long weekend"
                        }
                    }
                ],
                "x-plnnrly-permission": "furlough::create_for_yourself",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:furlough"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/FurloughBalanceMutationList",
                    "detail": "#/components/schemas/FurloughBalanceMutation"
                }
            },
            "x-plnnrly-allow": [
                "GET",
                "POST"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/furlough-balance-mutations/{furlough_balance_mutation}": {
            "get": {
                "operationId": "api.v2.furlough-balance-mutations.show",
                "summary": "Read a furlough balance mutation",
                "description": "Returns one mutation. The summary and detail views are identical on this resource: every field is small enough for a list row. `user` renders as an `{\"id\": N}` reference; `?include=user` expands it, and that expansion additionally requires `users::read` and a token scope covering the users prefix. `acceptor_id` has no relation to expand: look the approver up with `GET /api/v2/users/{user}`. As on the list operation, the furlough prefix has no read permission, so another employee's mutation answers `404`, never `403`, unless the caller holds `furlough::view_all` or `furlough::manage`.",
                "tags": [
                    "Leave"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:furlough"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:furlough"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "furlough_balance_mutation",
                        "in": "path",
                        "description": "The FurloughBalanceMutation's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/FurloughBalanceMutation"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "One mutation with the employee expanded",
                        "path": {
                            "furlough_balance_mutation": 1
                        },
                        "query": {
                            "include": "user"
                        }
                    }
                ],
                "x-plnnrly-permission": null,
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:read:furlough"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/FurloughBalanceMutationList",
                    "detail": "#/components/schemas/FurloughBalanceMutation"
                }
            },
            "patch": {
                "operationId": "api.v2.furlough-balance-mutations.update",
                "summary": "Update a furlough balance mutation",
                "description": "Partially updates a mutation: absent keys are untouched, an empty body is rejected, and an unknown key is a `422`. This is the API's only approval surface: `{\"is_approved\": true}` approves, `{\"is_approved\": false}` denies, and either needs `furlough::manage`, which is also this operation's own permission, so a plain employee cannot edit even their own pending request here although plnnrly lets them while it is still in the future. A manager deciding on their own mutation answers `403` `forbidden-by-policy`. ⚠ An approval through this endpoint is a plain field write: unlike the approval screen in plnnrly it does not split a request that spans two balance years into one mutation per year, and it sends the employee no e-mail. ⚠ `start` and `end` move as a pair (send both or neither), and neither rewrites `year`, so a period moved into another year keeps counting against the old balance year until `year` is sent too. Send `If-Match` with the `ETag` from your last read to detect a concurrent edit, which answers `412`. Treat it as best-effort only: this resource carries no concurrency token, and `updated_at` is second-precision, so two writes inside the same second are indistinguishable.",
                "tags": [
                    "Leave"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:furlough"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:furlough"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "furlough_balance_mutation",
                        "in": "path",
                        "description": "The FurloughBalanceMutation's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "year": {
                                        "type": "integer",
                                        "minimum": 1970,
                                        "maximum": 2200,
                                        "description": "The balance year this mutation counts against. It is not derived from `start`: every balance sum counts by this year, so a mutation whose period moves into another year keeps counting against the year sent here until `year` is sent too."
                                    },
                                    "type": {
                                        "type": "string",
                                        "enum": [
                                            "regular",
                                            "special"
                                        ],
                                        "x-enum-descriptions": {
                                            "regular": "Ordinary paid leave, drawn from the employee's regular annual balance.",
                                            "special": "Special leave: a separate balance granted for a specific entitlement, never offset against regular leave."
                                        },
                                        "x-extensible-enum": true,
                                        "description": "Which leave balance this mutation moves. Regular and special leave are separate balances and never offset one another."
                                    },
                                    "minutes": {
                                        "type": "integer",
                                        "x-plnnrly-grant-guard": "A positive value grants balance and requires the `furlough::manage` permission; a caller with only `furlough::create_for_yourself` may write a negative value (a leave request) or zero.",
                                        "x-plnnrly-special-leave-guard": "On a `special` mutation, the resulting special-leave balance for the year may not go negative.",
                                        "description": "The signed balance delta in minutes. Negative consumes balance (a leave request); positive grants it (a manager's adjustment, which requires the `furlough::manage` permission to write). The API never computes this from the employee's working week or the organisation's free days: send the exact number of minutes you mean."
                                    },
                                    "start": {
                                        "type": "string",
                                        "format": "date-time",
                                        "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$",
                                        "x-plnnrly-constraint": "Required when end is present.",
                                        "description": "Start of the period this mutation covers, in UTC. Equal to `end` on a manual balance adjustment, which covers no period at all."
                                    },
                                    "end": {
                                        "type": "string",
                                        "format": "date-time",
                                        "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$",
                                        "x-plnnrly-constraint": "Required when start is present. Must be on or after start.",
                                        "description": "End of the period this mutation covers, in UTC. Never before `start`, and equal to it on a manual balance adjustment."
                                    },
                                    "description": {
                                        "type": "string",
                                        "description": "The employee's or manager's own words for this mutation: the reason for the leave, or what the adjustment is for. The only field `?q=` searches."
                                    },
                                    "is_approved": {
                                        "type": [
                                            "boolean",
                                            "null"
                                        ],
                                        "description": "Approval state, in three values: `null` means pending approval, `true` approved, `false` denied. The balance counts pending and approved mutations alike and ignores denied ones. Writing this field at all requires the `furlough::manage` permission."
                                    }
                                },
                                "additionalProperties": false,
                                "minProperties": 1
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The updated record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/FurloughBalanceMutation"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Approve a pending leave request",
                        "path": {
                            "furlough_balance_mutation": 1
                        },
                        "body": {
                            "is_approved": true
                        },
                        "headers": {
                            "If-Match": "*"
                        }
                    },
                    {
                        "name": "Deny a pending leave request",
                        "path": {
                            "furlough_balance_mutation": 1
                        },
                        "body": {
                            "is_approved": false
                        }
                    },
                    {
                        "name": "Move the period and its balance year together",
                        "path": {
                            "furlough_balance_mutation": 1
                        },
                        "body": {
                            "year": 2027,
                            "start": "2027-01-04T00:00:00Z",
                            "end": "2027-01-05T23:59:59Z"
                        }
                    }
                ],
                "x-plnnrly-permission": "furlough::manage",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:furlough"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/FurloughBalanceMutationList",
                    "detail": "#/components/schemas/FurloughBalanceMutation"
                }
            },
            "delete": {
                "operationId": "api.v2.furlough-balance-mutations.destroy",
                "summary": "Delete a furlough balance mutation",
                "description": "Permanently deletes a mutation. This resource is hard-delete: there is no restore action, no `force` parameter and no `trashed` parameter. Nothing blocks the delete; the minutes simply stop counting against the balance year. This is plnnrly's withdraw and delete actions rolled into one, but it requires `furlough::manage`, so a plain employee cannot withdraw their own pending request through the API even though the history page in plnnrly lets them.",
                "tags": [
                    "Leave"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:destroy:furlough"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "furlough_balance_mutation",
                        "in": "path",
                        "description": "The FurloughBalanceMutation's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "responses": {
                    "204": {
                        "description": "Deleted. No body.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Withdraw a leave request",
                        "path": {
                            "furlough_balance_mutation": 1
                        }
                    }
                ],
                "x-plnnrly-permission": "furlough::manage",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:destroy:furlough"
                ]
            },
            "x-plnnrly-allow": [
                "GET",
                "PATCH",
                "DELETE"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/holidays": {
            "get": {
                "operationId": "api.v2.holidays.index",
                "summary": "List holidays",
                "description": "Returns the organisation's holiday templates, in the order plnnrly lists them: `sort_order` ascending. Search by name with q. A holiday carries no calendar date, so to find out when a holiday actually falls, follow `links.free_days`.",
                "tags": [
                    "Leave"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:holidays"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:holidays"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/PerPage"
                    },
                    {
                        "$ref": "#/components/parameters/Cursor"
                    },
                    {
                        "$ref": "#/components/parameters/HolidayPageNumber"
                    },
                    {
                        "$ref": "#/components/parameters/ViewSummary"
                    },
                    {
                        "name": "filter[created_at]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][gte]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][lt]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][gte]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][lt]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/HolidaySort"
                    },
                    {
                        "name": "q",
                        "in": "query",
                        "description": "Free-text search over `name`. At most one substring match per request: `q` together with a `contains` filter is 400 invalid-query-parameter.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "minLength": 1
                        }
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "A page of records.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record.",
                                "schema": {
                                    "type": "string",
                                    "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/HolidayList"
                                            }
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/HolidaysIndexMeta"
                                        },
                                        "links": {
                                            "$ref": "#/components/schemas/PageLinks"
                                        }
                                    },
                                    "required": [
                                        "data",
                                        "meta",
                                        "links"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, max-age=60",
                "x-plnnrly-examples": [
                    {
                        "name": "Christmas holidays by name",
                        "query": {
                            "q": "Kerst",
                            "sort": "name"
                        }
                    }
                ],
                "x-plnnrly-permission": "holidays::manage",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1,
                    "view_detail_total": 3,
                    "surcharges": {
                        "q": 2
                    }
                },
                "x-plnnrly-token-ability": [
                    "api:read:holidays"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/HolidayList",
                    "detail": "#/components/schemas/Holiday"
                }
            },
            "post": {
                "operationId": "api.v2.holidays.store",
                "summary": "Create a holiday",
                "description": "Creates a holiday template and returns it in the detail view with a `Location` header. Leave `default_month` and `default_day` out for a holiday that moves from year to year; the actual dates are recorded as free days afterwards. Creating a holiday whose name already exists succeeds: holiday names are not unique.",
                "tags": [
                    "Leave"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:holidays"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:holidays"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "description": "Name of the holiday, e.g. Nieuwjaarsdag."
                                    },
                                    "default_month": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "minimum": 1,
                                        "maximum": 12,
                                        "description": "Month the holiday normally falls in, 1-12. Null means the holiday moves from year to year (Easter, say), and the organisation fills the date in per year on the free day itself."
                                    },
                                    "default_day": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "minimum": 1,
                                        "maximum": 31,
                                        "description": "Day of the month the holiday normally falls on, 1-31, paired with `default_month`. Null means the day is not fixed."
                                    },
                                    "recurrence_interval": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "minimum": 1,
                                        "maximum": 255,
                                        "description": "How often the holiday repeats, in years: `null` is not recurring, 1 is yearly, and n repeats every n years."
                                    }
                                },
                                "required": [
                                    "name"
                                ],
                                "additionalProperties": false
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Created.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "Idempotency-Replayed": {
                                "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise.",
                                "schema": {
                                    "type": "boolean",
                                    "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise."
                                }
                            },
                            "Location": {
                                "description": "The absolute URL of the created or queued resource.",
                                "schema": {
                                    "type": "string",
                                    "description": "The absolute URL of the created or queued resource."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Holiday"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "$ref": "#/components/responses/Problem409Idempotency"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "A yearly fixed-date holiday",
                        "body": {
                            "name": "Nieuwjaarsdag",
                            "default_month": 1,
                            "default_day": 1,
                            "recurrence_interval": 1
                        }
                    },
                    {
                        "name": "A holiday whose date moves each year",
                        "body": {
                            "name": "Pasen"
                        }
                    }
                ],
                "x-plnnrly-permission": "holidays::manage",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:holidays"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/HolidayList",
                    "detail": "#/components/schemas/Holiday"
                }
            },
            "x-plnnrly-allow": [
                "GET",
                "POST"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/holidays/{holiday}": {
            "get": {
                "operationId": "api.v2.holidays.show",
                "summary": "Read a holiday",
                "description": "Returns one holiday in the detail view, including how many free days have been generated from it.",
                "tags": [
                    "Leave"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:holidays"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:holidays"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "holiday",
                        "in": "path",
                        "description": "The Holiday's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Holiday"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, max-age=60",
                "x-plnnrly-examples": [
                    {
                        "name": "One holiday",
                        "path": {
                            "holiday": 1
                        }
                    }
                ],
                "x-plnnrly-permission": "holidays::manage",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:read:holidays"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/HolidayList",
                    "detail": "#/components/schemas/Holiday"
                }
            },
            "patch": {
                "operationId": "api.v2.holidays.update",
                "summary": "Update a holiday",
                "description": "Partially updates a holiday: absent keys are untouched, and an explicit `null` clears `default_month`, `default_day` or `recurrence_interval`. `sort_order` is not writable: holidays are reordered in plnnrly. Send `If-Match` with the `ETag` from your last read to detect a concurrent edit, which answers `412`.",
                "tags": [
                    "Leave"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:holidays"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:holidays"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "holiday",
                        "in": "path",
                        "description": "The Holiday's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "description": "Name of the holiday, e.g. Nieuwjaarsdag."
                                    },
                                    "default_month": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "minimum": 1,
                                        "maximum": 12,
                                        "description": "Month the holiday normally falls in, 1-12. Null means the holiday moves from year to year (Easter, say), and the organisation fills the date in per year on the free day itself."
                                    },
                                    "default_day": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "minimum": 1,
                                        "maximum": 31,
                                        "description": "Day of the month the holiday normally falls on, 1-31, paired with `default_month`. Null means the day is not fixed."
                                    },
                                    "recurrence_interval": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "minimum": 1,
                                        "maximum": 255,
                                        "description": "How often the holiday repeats, in years: `null` is not recurring, 1 is yearly, and n repeats every n years."
                                    }
                                },
                                "additionalProperties": false,
                                "minProperties": 1
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The updated record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Holiday"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Turn a yearly holiday into a variable one",
                        "path": {
                            "holiday": 1
                        },
                        "body": {
                            "default_month": null,
                            "default_day": null
                        },
                        "headers": {
                            "If-Match": "*"
                        }
                    }
                ],
                "x-plnnrly-permission": "holidays::manage",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:holidays"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/HolidayList",
                    "detail": "#/components/schemas/Holiday"
                }
            },
            "delete": {
                "operationId": "api.v2.holidays.destroy",
                "summary": "Delete a holiday",
                "description": "Permanently deletes a holiday template; holidays do not soft-delete and there is no restore. The free days generated from it are not deleted: their `holiday_id` is set to `null`, so they survive as unlabelled days off. Detach or delete those free days first if you do not want them left behind.",
                "tags": [
                    "Leave"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:destroy:holidays"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "holiday",
                        "in": "path",
                        "description": "The Holiday's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "responses": {
                    "204": {
                        "description": "Deleted. No body.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Delete a holiday",
                        "path": {
                            "holiday": 1
                        }
                    }
                ],
                "x-plnnrly-permission": "holidays::manage",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:destroy:holidays"
                ]
            },
            "x-plnnrly-allow": [
                "GET",
                "PATCH",
                "DELETE"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/invoices": {
            "get": {
                "operationId": "api.v2.invoices.index",
                "summary": "List invoices",
                "description": "Returns the legacy per-project invoices, newest invoice date first. Every invoice in the organisation is visible to any caller holding `invoices::read`; there is no per-record rule. Scope to one project with `filter[project_id]`, to a period with `filter[date][gte]` and `filter[date][lte]`, and search with `q=` over the description. Defaults to the summary view; `view=detail` carries the same fields on this resource (none of them is detail-only) and costs three read units instead of one. Line items are not published, so no total appears here.",
                "tags": [
                    "Billing"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:invoices"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:invoices"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/PerPage"
                    },
                    {
                        "$ref": "#/components/parameters/Cursor"
                    },
                    {
                        "$ref": "#/components/parameters/PageNumber"
                    },
                    {
                        "$ref": "#/components/parameters/ViewSummary"
                    },
                    {
                        "name": "filter[created_at]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][gte]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][lt]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[date]",
                        "in": "query",
                        "description": "Invoice date. Combine gte and lte to select a period, e.g. one quarter.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[date][gte]",
                        "in": "query",
                        "description": "Invoice date. Combine gte and lte to select a period, e.g. one quarter.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[date][lte]",
                        "in": "query",
                        "description": "Invoice date. Combine gte and lte to select a period, e.g. one quarter.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[project_id]",
                        "in": "query",
                        "description": "Project the invoice was raised for. This is how you list one project's invoices: there is no nested path for them.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[project_id][in]",
                        "in": "query",
                        "description": "Project the invoice was raised for. This is how you list one project's invoices: there is no nested path for them. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[updated_at]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][gte]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][lt]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[user_id]",
                        "in": "query",
                        "description": "Employee the invoice is attributed to. `null` selects the invoices whose recorder has since been deleted.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[user_id][in]",
                        "in": "query",
                        "description": "Employee the invoice is attributed to. `null` selects the invoices whose recorder has since been deleted. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[user_id][null]",
                        "in": "query",
                        "description": "Employee the invoice is attributed to. `null` selects the invoices whose recorder has since been deleted. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/InvoiceSort"
                    },
                    {
                        "name": "q",
                        "in": "query",
                        "description": "Free-text search over `description`. At most one substring match per request: `q` together with a `contains` filter is 400 invalid-query-parameter.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "minLength": 1
                        }
                    },
                    {
                        "$ref": "#/components/parameters/InvoiceInclude"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "A page of records.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record.",
                                "schema": {
                                    "type": "string",
                                    "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/InvoiceList"
                                            }
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/InvoicesIndexMeta"
                                        },
                                        "links": {
                                            "$ref": "#/components/schemas/PageLinks"
                                        }
                                    },
                                    "required": [
                                        "data",
                                        "meta",
                                        "links"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "One quarter of a project's invoices",
                        "query": {
                            "filter[project_id]": 1,
                            "filter[date][gte]": "2026-01-01",
                            "filter[date][lte]": "2026-03-31",
                            "sort": "-date"
                        }
                    }
                ],
                "x-plnnrly-feature": "billing",
                "x-plnnrly-permission": "invoices::read",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1,
                    "view_detail_total": 3,
                    "surcharges": {
                        "include": 1,
                        "q": 2
                    }
                },
                "x-plnnrly-token-ability": [
                    "api:read:invoices"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/InvoiceList",
                    "detail": "#/components/schemas/Invoice"
                }
            },
            "post": {
                "operationId": "api.v2.invoices.store",
                "summary": "Create an invoice",
                "description": "Creates a legacy invoice against one project and returns it in the detail view with a `Location` header. All four body fields are required: none has a default. The invoice is attributed to the authenticated user; `user_id` is not writable and there is no on-behalf-of field, so an API-created invoice reads differently from the historical invoices, which carry the project manager instead. `project_id` is validated across into the projects prefix, so creating an invoice additionally requires the `projects::read` permission and a token scope covering the projects prefix; without either, `project_id` is refused as if it did not exist. Creating one bumps the project's `updated_at` and lands in the project's activity timeline. It has no line items and no way to add them (invoice rows are not part of this API), so the project's billed figure is unaffected.",
                "tags": [
                    "Billing"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:invoices"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:invoices"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "project_id": {
                                        "type": "integer",
                                        "x-plnnrly-references": {
                                            "resource": "Project",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "projects",
                                        "description": "Project this invoice was raised for."
                                    },
                                    "description": {
                                        "type": "string",
                                        "description": "What was invoiced, as free text. This is the field `?q=` searches."
                                    },
                                    "date": {
                                        "type": "string",
                                        "format": "date",
                                        "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                                        "description": "Invoice date, a zone-free calendar date. Newest first is the default sort."
                                    },
                                    "hourly_rate": {
                                        "type": "string",
                                        "format": "decimal",
                                        "pattern": "^\\d+(\\.\\d{1,2})?$",
                                        "x-plnnrly-constraint": "At least 0. At most 999999.99.",
                                        "x-plnnrly-maximum": 999999.99,
                                        "x-plnnrly-minimum": 0,
                                        "description": "Hourly rate this invoice was billed at, as a decimal string with two places. Never rounded or rescaled."
                                    }
                                },
                                "required": [
                                    "project_id",
                                    "description",
                                    "date",
                                    "hourly_rate"
                                ],
                                "additionalProperties": false
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Created.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "Idempotency-Replayed": {
                                "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise.",
                                "schema": {
                                    "type": "boolean",
                                    "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise."
                                }
                            },
                            "Location": {
                                "description": "The absolute URL of the created or queued resource.",
                                "schema": {
                                    "type": "string",
                                    "description": "The absolute URL of the created or queued resource."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Invoice"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "$ref": "#/components/responses/Problem409Idempotency"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Invoice a sprint at the standard rate",
                        "body": {
                            "project_id": 1,
                            "description": "Sprint 14: portal rebuild",
                            "date": "2026-03-31",
                            "hourly_rate": "85.00"
                        }
                    }
                ],
                "x-plnnrly-feature": "billing",
                "x-plnnrly-permission": "invoices::create",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:invoices"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/InvoiceList",
                    "detail": "#/components/schemas/Invoice"
                }
            },
            "x-plnnrly-allow": [
                "GET",
                "POST"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/invoices/{invoice}": {
            "get": {
                "operationId": "api.v2.invoices.show",
                "summary": "Read an invoice",
                "description": "Returns one invoice in the detail view. Invoices are hard-deleted and are removed with their project, so a deleted invoice is gone and answers `404`. The path takes the numeric id only: this resource has no alternate key, unlike a sales invoice, which can also be addressed by `invoice_number`.",
                "tags": [
                    "Billing"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:invoices"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:invoices"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "invoice",
                        "in": "path",
                        "description": "The Invoice's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Invoice"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "One invoice with its project and recorder",
                        "path": {
                            "invoice": 1
                        },
                        "query": {
                            "include": "project,user"
                        }
                    }
                ],
                "x-plnnrly-feature": "billing",
                "x-plnnrly-permission": "invoices::read",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:read:invoices"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/InvoiceList",
                    "detail": "#/components/schemas/Invoice"
                }
            },
            "x-plnnrly-allow": [
                "GET"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/labels": {
            "get": {
                "operationId": "api.v2.labels.index",
                "summary": "List labels",
                "description": "Returns the organisation's labels (the per-project tags work items are marked with) in each project's display order, with the id as the tiebreaker. The list covers the whole organisation and interleaves projects, because `order` is unique within a project only; narrow it with `filter[project_id]`. Listing additionally requires `project_management::manage_work_items`. Available only while the Sprints feature is enabled.",
                "tags": [
                    "Configuration"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:project_management"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:project_management"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/PerPage"
                    },
                    {
                        "$ref": "#/components/parameters/Cursor"
                    },
                    {
                        "$ref": "#/components/parameters/LabelPageNumber"
                    },
                    {
                        "$ref": "#/components/parameters/ViewSummary"
                    },
                    {
                        "name": "filter[created_at]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][gte]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][lt]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[project_id]",
                        "in": "query",
                        "description": "Narrow the list to one project's labels, or to several with `in`.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[project_id][in]",
                        "in": "query",
                        "description": "Narrow the list to one project's labels, or to several with `in`. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[updated_at]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][gte]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][lt]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[work_items.id]",
                        "in": "query",
                        "description": "The labels on one work item, by its numeric id. A work item's `links.labels` points here: it lists every label on the item, where `include=labels` stops at 25.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/LabelSort"
                    },
                    {
                        "name": "q",
                        "in": "query",
                        "description": "Free-text search over `name`. At most one substring match per request: `q` together with a `contains` filter is 400 invalid-query-parameter.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "minLength": 1
                        }
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "A page of records.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record.",
                                "schema": {
                                    "type": "string",
                                    "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/LabelList"
                                            }
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/LabelsIndexMeta"
                                        },
                                        "links": {
                                            "$ref": "#/components/schemas/PageLinks"
                                        }
                                    },
                                    "required": [
                                        "data",
                                        "meta",
                                        "links"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, max-age=60",
                "x-plnnrly-examples": [
                    {
                        "name": "One project's labels, in display order",
                        "query": {
                            "filter": {
                                "project_id": 1
                            },
                            "sort": "order"
                        }
                    }
                ],
                "x-plnnrly-feature": "sprints",
                "x-plnnrly-permission": "project_management::view_backlog",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1,
                    "view_detail_total": 3,
                    "surcharges": {
                        "q": 2
                    }
                },
                "x-plnnrly-token-ability": [
                    "api:read:project_management"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/LabelList",
                    "detail": "#/components/schemas/Label"
                }
            },
            "post": {
                "operationId": "api.v2.labels.store",
                "summary": "Create a label",
                "description": "Creates a label on a project and returns it with a `Location` header. `color` is optional: omit it and the server picks the first palette colour the project does not already use. `order` is not accepted: the new label is placed last within its project and is reordered only by dragging it in plnnrly. Attach the label to work items from the work-item side, with `label_ids`.",
                "tags": [
                    "Configuration"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:project_management"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:project_management"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "project_id": {
                                        "type": "integer",
                                        "x-plnnrly-references": {
                                            "resource": "Project",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "projects",
                                        "description": "The project this label belongs to. Follow `GET /api/v2/projects/{project}` for its details; the project is not expandable from here."
                                    },
                                    "name": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "description": "Label name as shown on the chip, e.g. `Blocked`."
                                    },
                                    "color": {
                                        "type": "string",
                                        "pattern": "^#[0-9a-fA-F]{6}\\z",
                                        "maxLength": 255,
                                        "description": "Six-digit hex colour, e.g. `#ef4444`. Stored verbatim: a row written before the colour picker enforced the format reads back as it is stored."
                                    },
                                    "icon": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "pattern": "^[A-Za-z0-9._-]+\\z",
                                        "maxLength": 255,
                                        "description": "Icon name, e.g. `heroicon-o-tag`: not a URL and not markup. Null when the label has no icon."
                                    }
                                },
                                "required": [
                                    "project_id",
                                    "name"
                                ],
                                "additionalProperties": false
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Created.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "Idempotency-Replayed": {
                                "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise.",
                                "schema": {
                                    "type": "boolean",
                                    "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise."
                                }
                            },
                            "Location": {
                                "description": "The absolute URL of the created or queued resource.",
                                "schema": {
                                    "type": "string",
                                    "description": "The absolute URL of the created or queued resource."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Label"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "$ref": "#/components/responses/Problem409Idempotency"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "A blocked label, colour left to the server",
                        "body": {
                            "project_id": 1,
                            "name": "Blocked"
                        }
                    }
                ],
                "x-plnnrly-feature": "sprints",
                "x-plnnrly-permission": "project_management::manage_work_items",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:project_management"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/LabelList",
                    "detail": "#/components/schemas/Label"
                }
            },
            "x-plnnrly-allow": [
                "GET",
                "POST"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/labels/{label}": {
            "get": {
                "operationId": "api.v2.labels.show",
                "summary": "Read a label",
                "description": "Returns one label. The summary and detail views are identical on this resource: every field is small enough for a list row, and nothing is includable, so `include` is rejected; follow `project_id` for the project. Reading additionally requires `project_management::manage_work_items`.",
                "tags": [
                    "Configuration"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:project_management"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:project_management"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "label",
                        "in": "path",
                        "description": "The Label's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Label"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, max-age=60",
                "x-plnnrly-examples": [
                    {
                        "name": "One label",
                        "path": {
                            "label": 1
                        }
                    }
                ],
                "x-plnnrly-feature": "sprints",
                "x-plnnrly-permission": "project_management::view_backlog",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:read:project_management"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/LabelList",
                    "detail": "#/components/schemas/Label"
                }
            },
            "patch": {
                "operationId": "api.v2.labels.update",
                "summary": "Update a label",
                "description": "Partially updates a label's name, colour or icon: absent keys are untouched, an empty body is rejected. `project_id` and `order` are not accepted: a label cannot be moved between projects, because its work-item attachments and its position are both scoped to the project it was created on. Renaming or recolouring a label notifies every work item carrying it, so a change to a widely-used label is proportionally slower. Send `If-Match` with the `ETag` from your last read to detect a concurrent edit, which answers `412`. Treat it as best-effort only: this resource carries no concurrency token, and `updated_at` is second-precision, so two writes inside the same second are indistinguishable.",
                "tags": [
                    "Configuration"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:project_management"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:project_management"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "label",
                        "in": "path",
                        "description": "The Label's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "description": "Label name as shown on the chip, e.g. `Blocked`."
                                    },
                                    "color": {
                                        "type": "string",
                                        "pattern": "^#[0-9a-fA-F]{6}\\z",
                                        "maxLength": 255,
                                        "description": "Six-digit hex colour, e.g. `#ef4444`. Stored verbatim: a row written before the colour picker enforced the format reads back as it is stored."
                                    },
                                    "icon": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "pattern": "^[A-Za-z0-9._-]+\\z",
                                        "maxLength": 255,
                                        "description": "Icon name, e.g. `heroicon-o-tag`: not a URL and not markup. Null when the label has no icon."
                                    }
                                },
                                "additionalProperties": false,
                                "minProperties": 1
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The updated record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Label"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Recolour a label",
                        "path": {
                            "label": 1
                        },
                        "body": {
                            "color": "#ef4444"
                        },
                        "headers": {
                            "If-Match": "*"
                        }
                    }
                ],
                "x-plnnrly-feature": "sprints",
                "x-plnnrly-permission": "project_management::manage_work_items",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:project_management"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/LabelList",
                    "detail": "#/components/schemas/Label"
                }
            },
            "delete": {
                "operationId": "api.v2.labels.destroy",
                "summary": "Delete a label",
                "description": "Permanently deletes a label. This resource is hard-delete: there is no restore action, no `force` parameter and no `trashed` parameter. The label silently disappears from every work item that carried it; the work items themselves are untouched. Each of those work items is notified, so deleting a widely-used label is proportionally slower. There is no in-use refusal: unlike a work type, a label in use can be deleted.",
                "tags": [
                    "Configuration"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:destroy:project_management"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "label",
                        "in": "path",
                        "description": "The Label's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "responses": {
                    "204": {
                        "description": "Deleted. No body.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Delete a label",
                        "path": {
                            "label": 1
                        }
                    }
                ],
                "x-plnnrly-feature": "sprints",
                "x-plnnrly-permission": "project_management::manage_work_items",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:destroy:project_management"
                ]
            },
            "x-plnnrly-allow": [
                "GET",
                "PATCH",
                "DELETE"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/me": {
            "get": {
                "operationId": "api.v2.me.show",
                "summary": "Read the token owner",
                "description": "Returns the user the token belongs to (the same person every `me` filter value stands for), so a client can show who it is connected as without knowing an id first. It needs a valid token but no permission: any token holding a read ability reaches it, whatever its prefix. The payload is the same `User` that `GET /users/{user}` returns: the detail view adds `working_hours` and `work_locations`, and `view=summary` leaves those two out. `links.self` points to that item, which does need `users::read`. A service account is never returned.",
                "tags": [
                    "Start here"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/User"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Who the token belongs to"
                    },
                    {
                        "name": "Without the working week and work locations",
                        "query": {
                            "view": "summary"
                        }
                    }
                ],
                "x-plnnrly-permission": null,
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:read"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/UserList",
                    "detail": "#/components/schemas/User"
                }
            },
            "x-plnnrly-allow": [
                "GET"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/notes": {
            "get": {
                "operationId": "api.v2.notes.index",
                "summary": "List notes",
                "description": "Returns the notes whose parent record the caller may read, oldest position first. A note is visible exactly when the record it hangs off is: only notes on projects, customers and work items you can see are listed, so this collection can be read across the whole organisation without leaking a record the caller cannot open. Scope it to one record with the `notable_type`/`notable_id` pair, which must be sent together. `total` is omitted unless you pass `with_total=1`. The declared permission is `project_drive::access`; the other three drive prefixes (`customer_drive`, `organization_drive`, `user_drive`) apply by owner type, and a token covering any of the four is accepted.",
                "tags": [
                    "Drive"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:project_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:read:customer_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:read:organization_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:read:user_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:project_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:customer_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:organization_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:user_drive"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/PerPage"
                    },
                    {
                        "$ref": "#/components/parameters/Cursor"
                    },
                    {
                        "$ref": "#/components/parameters/ViewSummary"
                    },
                    {
                        "$ref": "#/components/parameters/WithTotal"
                    },
                    {
                        "name": "filter[created_at]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][gte]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][lt]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[notable_id]",
                        "in": "query",
                        "description": "Id of the record the note is attached to, within the `notable_type` given. Must be sent together with `notable_type`.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        },
                        "x-plnnrly-constraint": "Send notable_id and notable_type together."
                    },
                    {
                        "name": "filter[notable_id][in]",
                        "in": "query",
                        "description": "Id of the record the note is attached to, within the `notable_type` given. Must be sent together with `notable_type`. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false,
                        "x-plnnrly-constraint": "Send notable_id and notable_type together."
                    },
                    {
                        "name": "filter[notable_type]",
                        "in": "query",
                        "description": "Type of record the note is attached to: `project`, `customer` or `work_item`. Must be sent together with `notable_id`.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "x-extensible-enum": true
                        },
                        "x-plnnrly-constraint": "Send notable_type and notable_id together."
                    },
                    {
                        "name": "filter[updated_at]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][gte]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][lt]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[user_id]",
                        "in": "query",
                        "description": "Author of the note. Accepts the literal `me`.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[user_id][in]",
                        "in": "query",
                        "description": "Author of the note. Accepts the literal `me`. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[user_id][null]",
                        "in": "query",
                        "description": "Author of the note. Accepts the literal `me`. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/NoteSort"
                    },
                    {
                        "$ref": "#/components/parameters/NoteInclude"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "A page of records.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record.",
                                "schema": {
                                    "type": "string",
                                    "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/NoteList"
                                            }
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/NotesIndexMeta"
                                        },
                                        "links": {
                                            "$ref": "#/components/schemas/PageLinks"
                                        }
                                    },
                                    "required": [
                                        "data",
                                        "meta",
                                        "links"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "One project's notes with their authors",
                        "query": {
                            "filter": {
                                "notable_type": "project",
                                "notable_id": 1
                            },
                            "include": "user",
                            "with_total": 1
                        }
                    }
                ],
                "x-plnnrly-permission": "project_drive::access",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1,
                    "view_detail_total": 3,
                    "surcharges": {
                        "with_total": 2,
                        "include": 1
                    }
                },
                "x-plnnrly-token-ability": [
                    "api:read:project_drive",
                    "api:read:customer_drive",
                    "api:read:organization_drive",
                    "api:read:user_drive"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/NoteList",
                    "detail": "#/components/schemas/Note"
                }
            },
            "post": {
                "operationId": "api.v2.notes.store",
                "summary": "Create a note",
                "description": "Attaches a note to a project, a customer or a work item and returns it with a `Location` header, mirroring the Notes sub-page in plnnrly. The parent is named by the `notable_type`/`notable_id` pair (one of the few flat creates that accepts a type and id pair), and a parent the caller cannot read is refused exactly as a nonexistent one is. Writing a note also needs the parent's own note permission, as in plnnrly: `projects::notes` for a project, `customers::update` for a customer and `project_management::manage_work_items` for a work item; without it the request is `403` `forbidden-by-policy`. The author is always the authenticated user and cannot be set; the sort position is assigned by the server. The declared permission is `project_drive::create`; the other three drive prefixes apply by owner type.",
                "tags": [
                    "Drive"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:project_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:write:customer_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:write:organization_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:write:user_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:project_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:customer_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:organization_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:user_drive"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "content": {
                                        "type": "string",
                                        "maxLength": 500000,
                                        "description": "Note body as rich-text HTML, sanitised on read."
                                    },
                                    "notable_type": {
                                        "type": "string",
                                        "enum": [
                                            "project",
                                            "customer",
                                            "work_item"
                                        ],
                                        "description": "The project, customer or work item this note is attached to."
                                    },
                                    "notable_id": {
                                        "type": "integer",
                                        "description": "The project, customer or work item this note is attached to."
                                    }
                                },
                                "required": [
                                    "content",
                                    "notable_type",
                                    "notable_id"
                                ],
                                "additionalProperties": false
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Created.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "Idempotency-Replayed": {
                                "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise.",
                                "schema": {
                                    "type": "boolean",
                                    "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise."
                                }
                            },
                            "Location": {
                                "description": "The absolute URL of the created or queued resource.",
                                "schema": {
                                    "type": "string",
                                    "description": "The absolute URL of the created or queued resource."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Note"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "$ref": "#/components/responses/Problem409Idempotency"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "A note on a project",
                        "body": {
                            "notable_type": "project",
                            "notable_id": 1,
                            "content": "<p>Kick-off moved to Monday; the client will confirm scope by Friday.</p>"
                        }
                    }
                ],
                "x-plnnrly-permission": "project_drive::create",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:project_drive",
                    "api:write:customer_drive",
                    "api:write:organization_drive",
                    "api:write:user_drive"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/NoteList",
                    "detail": "#/components/schemas/Note"
                }
            },
            "x-plnnrly-allow": [
                "GET",
                "POST"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/notes/{note}": {
            "get": {
                "operationId": "api.v2.notes.show",
                "summary": "Read a note",
                "description": "Returns one note in the detail view. The summary and detail payloads are identical on this resource: the body is rendered in both, because plnnrly shows note bodies inline in its list. `notable` and `user` render as {\"id\": N} references until named in include. There is no soft delete: a deleted note is gone, and `trashed` is not an accepted parameter. The declared permission is `project_drive::access`; the other three drive prefixes apply by owner type, and whether you can see the parent record is what actually decides.",
                "tags": [
                    "Drive"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:project_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:read:customer_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:read:organization_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:read:user_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:project_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:customer_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:organization_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:user_drive"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "note",
                        "in": "path",
                        "description": "The Note's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Note"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "One note with its parent record expanded",
                        "path": {
                            "note": 1
                        },
                        "query": {
                            "include": "notable,user"
                        }
                    }
                ],
                "x-plnnrly-permission": "project_drive::access",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:read:project_drive",
                    "api:read:customer_drive",
                    "api:read:organization_drive",
                    "api:read:user_drive"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/NoteList",
                    "detail": "#/components/schemas/Note"
                }
            },
            "patch": {
                "operationId": "api.v2.notes.update",
                "summary": "Update a note",
                "description": "Partially updates a note: absent keys are untouched, an empty body is rejected. `content` is the only writable key: a note cannot be re-parented, so neither half of the notable pair is accepted, matching plnnrly, whose only per-note edit action writes the body. Editing needs the parent's note permission (`projects::notes`, `customers::update` or `project_management::manage_work_items` by parent type), or the request is `403` `forbidden-by-policy`. Writing a note touches its parent record's `updated_at`. Send `If-Match` with the `ETag` from your last read to detect a concurrent edit, which answers `412`. Treat it as best-effort only: this resource carries no concurrency token and `updated_at` is second-precision, so two writes inside the same second are indistinguishable. The declared permission is `project_drive::create` (editing is part of the create permission), and the other three drive prefixes apply by owner type.",
                "tags": [
                    "Drive"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:project_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:write:customer_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:write:organization_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:write:user_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:project_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:customer_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:organization_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:user_drive"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "note",
                        "in": "path",
                        "description": "The Note's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "content": {
                                        "type": "string",
                                        "maxLength": 500000,
                                        "description": "Note body as rich-text HTML, sanitised on read."
                                    }
                                },
                                "additionalProperties": false,
                                "minProperties": 1
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The updated record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Note"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Rewrite the body",
                        "path": {
                            "note": 1
                        },
                        "body": {
                            "content": "<p>Kick-off confirmed for Monday 09:00.</p>"
                        },
                        "headers": {
                            "If-Match": "*"
                        }
                    }
                ],
                "x-plnnrly-permission": "project_drive::create",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:project_drive",
                    "api:write:customer_drive",
                    "api:write:organization_drive",
                    "api:write:user_drive"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/NoteList",
                    "detail": "#/components/schemas/Note"
                }
            },
            "delete": {
                "operationId": "api.v2.notes.destroy",
                "summary": "Delete a note",
                "description": "Permanently deletes a note. This resource is hard-delete: there is no restore action, no `force` parameter and no `trashed` parameter. Deleting an already-deleted note answers `404`. Deleting needs the parent's note permission (`projects::notes`, `customers::update` or `project_management::manage_work_items` by parent type), or the request is `403` `forbidden-by-policy`. The parent record is unaffected. The declared permission is `project_drive::create` (the same permission as editing), and the other three drive prefixes apply by owner type.",
                "tags": [
                    "Drive"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:destroy:project_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:destroy:customer_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:destroy:organization_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:destroy:user_drive"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "note",
                        "in": "path",
                        "description": "The Note's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "responses": {
                    "204": {
                        "description": "Deleted. No body.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Remove a note",
                        "path": {
                            "note": 1
                        }
                    }
                ],
                "x-plnnrly-permission": "project_drive::create",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:destroy:project_drive",
                    "api:destroy:customer_drive",
                    "api:destroy:organization_drive",
                    "api:destroy:user_drive"
                ]
            },
            "x-plnnrly-allow": [
                "GET",
                "PATCH",
                "DELETE"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/project-links": {
            "get": {
                "operationId": "api.v2.project-links.index",
                "summary": "List project links",
                "description": "Returns the organisation's project links, sorted by name A→Z. Pass `filter[project_id]` to read one project's links: there is no `projects/{project}/project-links` path. Every project link operation requires the `projects::links` permission, not the projects prefix's usual create, read, update and delete permissions.",
                "tags": [
                    "Projects"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:projects"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:projects"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/PerPage"
                    },
                    {
                        "$ref": "#/components/parameters/Cursor"
                    },
                    {
                        "$ref": "#/components/parameters/PageNumber"
                    },
                    {
                        "$ref": "#/components/parameters/ViewSummary"
                    },
                    {
                        "name": "filter[created_at]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][gte]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][lt]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[project_id]",
                        "in": "query",
                        "description": "Owning project. This is how you list one project's links: there is no nested path for them.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[project_id][in]",
                        "in": "query",
                        "description": "Owning project. This is how you list one project's links: there is no nested path for them. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[updated_at]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][gte]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][lt]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ProjectLinkSort"
                    },
                    {
                        "name": "q",
                        "in": "query",
                        "description": "Free-text search over `name`. At most one substring match per request: `q` together with a `contains` filter is 400 invalid-query-parameter.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "minLength": 1
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ProjectLinkInclude"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "A page of records.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record.",
                                "schema": {
                                    "type": "string",
                                    "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/ProjectLinkList"
                                            }
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/ProjectLinksIndexMeta"
                                        },
                                        "links": {
                                            "$ref": "#/components/schemas/PageLinks"
                                        }
                                    },
                                    "required": [
                                        "data",
                                        "meta",
                                        "links"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Search one project's links by name",
                        "query": {
                            "filter[project_id]": 1,
                            "q": "staging",
                            "sort": "name"
                        }
                    }
                ],
                "x-plnnrly-permission": "projects::links",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1,
                    "view_detail_total": 3,
                    "surcharges": {
                        "include": 1,
                        "q": 2
                    }
                },
                "x-plnnrly-token-ability": [
                    "api:read:projects"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/ProjectLinkList",
                    "detail": "#/components/schemas/ProjectLink"
                }
            },
            "post": {
                "operationId": "api.v2.project-links.store",
                "summary": "Create a project link",
                "description": "Pins a named URL to the project named in the body and returns it with a `Location` header. `project_id`, name and url are all required, and a link cannot be created against a soft-deleted project. The write also bumps the owning project's `updated_at`, so the project's `ETag` changes and it reappears in an incremental `filter[updated_at][gte]` window, and it is recorded in the project's activity timeline rather than a timeline of its own. Every project link operation requires the `projects::links` permission, not the projects prefix's usual create, read, update and delete permissions.",
                "tags": [
                    "Projects"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:projects"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:projects"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "project_id": {
                                        "type": "integer",
                                        "x-plnnrly-references": {
                                            "resource": "Project",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "projects",
                                        "description": "Owning project."
                                    },
                                    "name": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "description": "Link name as shown in the UI, e.g. `Staging`. This is the default sort key."
                                    },
                                    "url": {
                                        "type": "string",
                                        "format": "uri",
                                        "description": "The link target, e.g. `https://staging.example.test`."
                                    }
                                },
                                "required": [
                                    "project_id",
                                    "name",
                                    "url"
                                ],
                                "additionalProperties": false
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Created.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "Idempotency-Replayed": {
                                "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise.",
                                "schema": {
                                    "type": "boolean",
                                    "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise."
                                }
                            },
                            "Location": {
                                "description": "The absolute URL of the created or queued resource.",
                                "schema": {
                                    "type": "string",
                                    "description": "The absolute URL of the created or queued resource."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/ProjectLink"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "$ref": "#/components/responses/Problem409Idempotency"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Pin a staging environment",
                        "body": {
                            "project_id": 1,
                            "name": "Staging",
                            "url": "https://staging.example.test"
                        }
                    }
                ],
                "x-plnnrly-permission": "projects::links",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:projects"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/ProjectLinkList",
                    "detail": "#/components/schemas/ProjectLink"
                }
            },
            "x-plnnrly-allow": [
                "GET",
                "POST"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/project-links/{project_link}": {
            "get": {
                "operationId": "api.v2.project-links.show",
                "summary": "Read a project link",
                "description": "Returns one project link. Every project link operation requires the `projects::links` permission, not the projects prefix's usual create, read, update and delete permissions.",
                "tags": [
                    "Projects"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:projects"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:projects"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "project_link",
                        "in": "path",
                        "description": "The ProjectLink's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/ProjectLink"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "One project link",
                        "path": {
                            "project_link": 1
                        }
                    }
                ],
                "x-plnnrly-permission": "projects::links",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:read:projects"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/ProjectLinkList",
                    "detail": "#/components/schemas/ProjectLink"
                }
            },
            "patch": {
                "operationId": "api.v2.project-links.update",
                "summary": "Update a project link",
                "description": "Partially updates a project link's name and url: absent keys are untouched and an empty body is rejected. `project_id` is not accepted, so a link cannot be moved between projects. The write bumps the owning project's `updated_at` and lands in the project's activity timeline. Send `If-Match` with the `ETag` from your last read to detect a concurrent edit, which answers `412`. Every project link operation requires the `projects::links` permission, not the projects prefix's usual create, read, update and delete permissions.",
                "tags": [
                    "Projects"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:projects"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:projects"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "project_link",
                        "in": "path",
                        "description": "The ProjectLink's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "description": "Link name as shown in the UI, e.g. `Staging`. This is the default sort key."
                                    },
                                    "url": {
                                        "type": "string",
                                        "format": "uri",
                                        "description": "The link target, e.g. `https://staging.example.test`."
                                    }
                                },
                                "additionalProperties": false,
                                "minProperties": 1
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The updated record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/ProjectLink"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Rename a link",
                        "path": {
                            "project_link": 1
                        },
                        "body": {
                            "name": "Staging (EU)"
                        },
                        "headers": {
                            "If-Match": "*"
                        }
                    }
                ],
                "x-plnnrly-permission": "projects::links",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:projects"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/ProjectLinkList",
                    "detail": "#/components/schemas/ProjectLink"
                }
            },
            "delete": {
                "operationId": "api.v2.project-links.destroy",
                "summary": "Delete a project link",
                "description": "Deletes a project link permanently: it uses no soft deletes, so there is no restore and no force parameter. The delete bumps the owning project's `updated_at` and is recorded in the project's activity timeline. The bulk delete in plnnrly maps to one DELETE per link; there is no bulk endpoint. Every project link operation requires the `projects::links` permission, not the projects prefix's usual create, read, update and delete permissions.",
                "tags": [
                    "Projects"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:destroy:projects"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "project_link",
                        "in": "path",
                        "description": "The ProjectLink's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "responses": {
                    "204": {
                        "description": "Deleted. No body.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Delete a project link",
                        "path": {
                            "project_link": 1
                        }
                    }
                ],
                "x-plnnrly-permission": "projects::links",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:destroy:projects"
                ]
            },
            "x-plnnrly-allow": [
                "GET",
                "PATCH",
                "DELETE"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/projects": {
            "get": {
                "operationId": "api.v2.projects.index",
                "summary": "List projects",
                "description": "Returns the projects the caller may read, with their customer, sub-customer, project manager and account manager inlined. The default sort is title A→Z, not recent activity; pass `sort=-last_activity` for that. Defaults to the summary view; pass `view=detail` for the full payload at three read units instead of one. Pass `include=logged_minutes` for the worklog total the caller may see.",
                "tags": [
                    "Projects"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:projects"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:projects"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/PerPage"
                    },
                    {
                        "$ref": "#/components/parameters/Cursor"
                    },
                    {
                        "$ref": "#/components/parameters/PageNumber"
                    },
                    {
                        "$ref": "#/components/parameters/ViewSummary"
                    },
                    {
                        "$ref": "#/components/parameters/Trashed"
                    },
                    {
                        "name": "filter[account_manager_id]",
                        "in": "query",
                        "description": "Responsible account manager. Accepts the literal `me`.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[account_manager_id][in]",
                        "in": "query",
                        "description": "Responsible account manager. Accepts the literal `me`. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[account_manager_id][null]",
                        "in": "query",
                        "description": "Responsible account manager. Accepts the literal `me`. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[application_id]",
                        "in": "query",
                        "description": "Linked application, managed by platform administrators.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[application_id][in]",
                        "in": "query",
                        "description": "Linked application, managed by platform administrators. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[application_id][null]",
                        "in": "query",
                        "description": "Linked application, managed by platform administrators. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[created_at]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][gte]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][lt]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[customer.code]",
                        "in": "query",
                        "description": "Short code of the owning customer, e.g. ACM.",
                        "required": false,
                        "schema": {
                            "type": "string"
                        },
                        "x-plnnrly-requires": {
                            "permission": "customers::read",
                            "abilities": [
                                "api:read:customers"
                            ]
                        }
                    },
                    {
                        "name": "filter[customer_id]",
                        "in": "query",
                        "description": "Owning customer.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[customer_id][in]",
                        "in": "query",
                        "description": "Owning customer. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[customer_id][null]",
                        "in": "query",
                        "description": "Owning customer. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[end_date]",
                        "in": "query",
                        "description": "Last day of the project.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[end_date][gte]",
                        "in": "query",
                        "description": "Last day of the project.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[end_date][lte]",
                        "in": "query",
                        "description": "Last day of the project.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[end_date][null]",
                        "in": "query",
                        "description": "Last day of the project. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[is_billable]",
                        "in": "query",
                        "description": "Whether time is billable.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[is_continuous]",
                        "in": "query",
                        "description": "Whether the project is open-ended.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[last_activity]",
                        "in": "query",
                        "description": "Most recent write anywhere in the project.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[last_activity][gte]",
                        "in": "query",
                        "description": "Most recent write anywhere in the project.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[last_activity][lt]",
                        "in": "query",
                        "description": "Most recent write anywhere in the project.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[last_activity][null]",
                        "in": "query",
                        "description": "Most recent write anywhere in the project. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[project_manager_id]",
                        "in": "query",
                        "description": "Responsible project manager. Accepts the literal `me`.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[project_manager_id][in]",
                        "in": "query",
                        "description": "Responsible project manager. Accepts the literal `me`. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[project_manager_id][null]",
                        "in": "query",
                        "description": "Responsible project manager. Accepts the literal `me`. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[start_date]",
                        "in": "query",
                        "description": "First day of the project.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[start_date][gte]",
                        "in": "query",
                        "description": "First day of the project.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[start_date][lte]",
                        "in": "query",
                        "description": "First day of the project.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[start_date][null]",
                        "in": "query",
                        "description": "First day of the project. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[status]",
                        "in": "query",
                        "description": "Lifecycle status of the project.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "active",
                                "finished",
                                "invoiced",
                                "on-hold",
                                "inactive"
                            ],
                            "x-extensible-enum": true
                        }
                    },
                    {
                        "name": "filter[status][ne]",
                        "in": "query",
                        "description": "Lifecycle status of the project.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "active",
                                "finished",
                                "invoiced",
                                "on-hold",
                                "inactive"
                            ],
                            "x-extensible-enum": true
                        }
                    },
                    {
                        "name": "filter[status][in]",
                        "in": "query",
                        "description": "Lifecycle status of the project. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "string",
                                "enum": [
                                    "active",
                                    "finished",
                                    "invoiced",
                                    "on-hold",
                                    "inactive"
                                ],
                                "x-extensible-enum": true
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[sub_customer_id]",
                        "in": "query",
                        "description": "Sub-customer.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[sub_customer_id][in]",
                        "in": "query",
                        "description": "Sub-customer. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[sub_customer_id][null]",
                        "in": "query",
                        "description": "Sub-customer. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[tags.title]",
                        "in": "query",
                        "description": "Projects carrying this exact tag title.",
                        "required": false,
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "filter[updated_at]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][gte]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][lt]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[workflow_id]",
                        "in": "query",
                        "description": "Workflow the project's work items move through.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[workflow_id][in]",
                        "in": "query",
                        "description": "Workflow the project's work items move through. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[workflow_id][null]",
                        "in": "query",
                        "description": "Workflow the project's work items move through. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[workflow_mode]",
                        "in": "query",
                        "description": "Sprints or a simple board.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "sprints",
                                "simple"
                            ],
                            "x-extensible-enum": true
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ProjectSort"
                    },
                    {
                        "name": "q",
                        "in": "query",
                        "description": "Free-text search over `title`. At most one substring match per request: `q` together with a `contains` filter is 400 invalid-query-parameter.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "minLength": 1
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ProjectInclude"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "A page of records.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record.",
                                "schema": {
                                    "type": "string",
                                    "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/ProjectList"
                                            }
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/ProjectsIndexMeta"
                                        },
                                        "links": {
                                            "$ref": "#/components/schemas/PageLinks"
                                        }
                                    },
                                    "required": [
                                        "data",
                                        "meta",
                                        "links"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Active projects for one customer",
                        "query": {
                            "filter[status]": "active",
                            "filter[customer_id]": 1,
                            "sort": "-start_date"
                        }
                    }
                ],
                "x-plnnrly-permission": "projects::read",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1,
                    "view_detail_total": 3,
                    "surcharges": {
                        "include": 1,
                        "include_to_many": 3,
                        "q": 2
                    }
                },
                "x-plnnrly-token-ability": [
                    "api:read:projects"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/ProjectList",
                    "detail": "#/components/schemas/Project"
                }
            },
            "post": {
                "operationId": "api.v2.projects.store",
                "summary": "Create a project",
                "description": "Creates a project and returns it in the detail view with a `Location` header. The account manager defaults to the authenticated user and cannot be set directly. `workflow_mode` is required even on an organisation that does not use sprints, and the project's workflow itself is configured in plnnrly rather than here. Pass `tag_ids` to set the project's tags; the set is written whole.",
                "tags": [
                    "Projects"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:projects"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:projects"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "title": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "description": "Project title as shown in the UI."
                                    },
                                    "description": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "description": "Free-text description. HTML from the rich editor, sanitised."
                                    },
                                    "status": {
                                        "type": "string",
                                        "enum": [
                                            "active",
                                            "finished",
                                            "invoiced",
                                            "on-hold",
                                            "inactive"
                                        ],
                                        "x-enum-descriptions": {
                                            "active": "Work is running.",
                                            "finished": "Delivered; nothing further is planned.",
                                            "inactive": "Archived, kept for history and not worked on.",
                                            "invoiced": "Finished and billed. Setting this finishes every task on every budget of the project.",
                                            "on-hold": "Paused, and expected to resume."
                                        },
                                        "x-extensible-enum": true,
                                        "description": "Lifecycle status. Setting it to invoiced finishes every task on every budget of the project."
                                    },
                                    "workflow_mode": {
                                        "type": "string",
                                        "enum": [
                                            "sprints",
                                            "simple"
                                        ],
                                        "x-enum-descriptions": {
                                            "simple": "A single board with workflow columns, without sprints or a backlog.",
                                            "sprints": "Work is planned in sprints, with a separate backlog and an active-sprint board."
                                        },
                                        "x-extensible-enum": true,
                                        "description": "Whether work is planned in sprints or on a simple board."
                                    },
                                    "start_date": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "format": "date",
                                        "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                                        "description": "First day of the project."
                                    },
                                    "end_date": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "format": "date",
                                        "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                                        "x-plnnrly-constraint": "Must be on or after start_date.",
                                        "description": "Last day, `null` if open-ended."
                                    },
                                    "is_billable": {
                                        "type": "boolean",
                                        "description": "Whether time on this project is billable."
                                    },
                                    "is_continuous": {
                                        "type": "boolean",
                                        "description": "Whether the project has no planned end."
                                    },
                                    "customer_id": {
                                        "type": "integer",
                                        "x-plnnrly-references": {
                                            "resource": "Customer",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "customers",
                                        "description": "Owning customer."
                                    },
                                    "sub_customer_id": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "x-plnnrly-references": {
                                            "resource": "Customer",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "customers",
                                        "description": "Optional sub-customer."
                                    },
                                    "project_manager_id": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "x-plnnrly-references": {
                                            "resource": "User",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "users",
                                        "description": "Responsible project manager."
                                    },
                                    "application_id": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "x-plnnrly-references": {
                                            "field": "id"
                                        },
                                        "description": "Linked application. Writable only while the organisation has the Infrastructure feature: sending a non-`null` value without it is a `422` `prohibited`. Null clears the link regardless."
                                    },
                                    "tag_ids": {
                                        "type": "array",
                                        "items": {
                                            "type": "integer",
                                            "x-plnnrly-references": {
                                                "resource": "Tag",
                                                "field": "id"
                                            }
                                        },
                                        "maxItems": 50,
                                        "description": "Tags on this project. A project takes at most 50 tags, so the include lists them all. `tag_ids` replaces the whole set."
                                    }
                                },
                                "required": [
                                    "title",
                                    "status",
                                    "workflow_mode",
                                    "is_billable",
                                    "is_continuous",
                                    "customer_id"
                                ],
                                "additionalProperties": false
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Created.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "Idempotency-Replayed": {
                                "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise.",
                                "schema": {
                                    "type": "boolean",
                                    "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise."
                                }
                            },
                            "Location": {
                                "description": "The absolute URL of the created or queued resource.",
                                "schema": {
                                    "type": "string",
                                    "description": "The absolute URL of the created or queued resource."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Project"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "$ref": "#/components/responses/Problem409Idempotency"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "A billable sprint project",
                        "body": {
                            "title": "Portal rebuild",
                            "status": "active",
                            "workflow_mode": "sprints",
                            "is_billable": true,
                            "is_continuous": false,
                            "customer_id": 1
                        }
                    }
                ],
                "x-plnnrly-permission": "projects::create",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:projects"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/ProjectList",
                    "detail": "#/components/schemas/Project"
                }
            },
            "x-plnnrly-allow": [
                "GET",
                "POST"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/projects/{project}": {
            "get": {
                "operationId": "api.v2.projects.show",
                "summary": "Read a project",
                "description": "Returns one project in the detail view, with its counts of sprints, budgets, tags and visible kanban tasks, and links to those collections. A trashed project is `404` unless `trashed=with` is passed and the caller holds `projects::read`.",
                "tags": [
                    "Projects"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:projects"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:projects"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "project",
                        "in": "path",
                        "description": "The Project's numeric id. Accepts a soft-deleted record.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/Trashed"
                    },
                    {
                        "$ref": "#/components/parameters/Meta"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Project"
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/ProjectsShowMeta"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "One project with its tags",
                        "path": {
                            "project": 1
                        },
                        "query": {
                            "include": "tags"
                        }
                    }
                ],
                "x-plnnrly-permission": "projects::read",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:read:projects"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/ProjectList",
                    "detail": "#/components/schemas/Project"
                }
            },
            "patch": {
                "operationId": "api.v2.projects.update",
                "summary": "Update a project",
                "description": "Partially updates a project: absent keys are untouched and an empty body is rejected. Setting status to invoiced also finishes every task on every budget of the project, in the same write. `account_manager_id` and `application_id` are not accepted, and a project's workflow is changed in plnnrly. `tag_ids` replaces the tag set whole; an empty array clears it. Send `If-Match` with the `ETag` from your last read to detect a concurrent edit, which answers `412`.",
                "tags": [
                    "Projects"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:projects"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:projects"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "project",
                        "in": "path",
                        "description": "The Project's numeric id. Accepts a soft-deleted record.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/Trashed"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "title": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "description": "Project title as shown in the UI."
                                    },
                                    "description": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "description": "Free-text description. HTML from the rich editor, sanitised."
                                    },
                                    "status": {
                                        "type": "string",
                                        "enum": [
                                            "active",
                                            "finished",
                                            "invoiced",
                                            "on-hold",
                                            "inactive"
                                        ],
                                        "x-enum-descriptions": {
                                            "active": "Work is running.",
                                            "finished": "Delivered; nothing further is planned.",
                                            "inactive": "Archived, kept for history and not worked on.",
                                            "invoiced": "Finished and billed. Setting this finishes every task on every budget of the project.",
                                            "on-hold": "Paused, and expected to resume."
                                        },
                                        "x-extensible-enum": true,
                                        "description": "Lifecycle status. Setting it to invoiced finishes every task on every budget of the project."
                                    },
                                    "workflow_mode": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "enum": [
                                            "sprints",
                                            "simple",
                                            null
                                        ],
                                        "x-enum-descriptions": {
                                            "simple": "A single board with workflow columns, without sprints or a backlog.",
                                            "sprints": "Work is planned in sprints, with a separate backlog and an active-sprint board."
                                        },
                                        "x-extensible-enum": true,
                                        "description": "Whether work is planned in sprints or on a simple board."
                                    },
                                    "start_date": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "format": "date",
                                        "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                                        "description": "First day of the project."
                                    },
                                    "end_date": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "format": "date",
                                        "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                                        "x-plnnrly-constraint": "Must be on or after start_date.",
                                        "description": "Last day, `null` if open-ended."
                                    },
                                    "is_billable": {
                                        "type": "boolean",
                                        "description": "Whether time on this project is billable."
                                    },
                                    "is_continuous": {
                                        "type": "boolean",
                                        "description": "Whether the project has no planned end."
                                    },
                                    "customer_id": {
                                        "type": "integer",
                                        "x-plnnrly-references": {
                                            "resource": "Customer",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "customers",
                                        "description": "Owning customer."
                                    },
                                    "sub_customer_id": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "x-plnnrly-references": {
                                            "resource": "Customer",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "customers",
                                        "description": "Optional sub-customer."
                                    },
                                    "project_manager_id": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "x-plnnrly-references": {
                                            "resource": "User",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "users",
                                        "description": "Responsible project manager."
                                    },
                                    "application_id": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "x-plnnrly-references": {
                                            "field": "id"
                                        },
                                        "description": "Linked application. Writable only while the organisation has the Infrastructure feature: sending a non-`null` value without it is a `422` `prohibited`. Null clears the link regardless."
                                    },
                                    "tag_ids": {
                                        "type": "array",
                                        "items": {
                                            "type": "integer",
                                            "x-plnnrly-references": {
                                                "resource": "Tag",
                                                "field": "id"
                                            }
                                        },
                                        "maxItems": 50,
                                        "description": "Tags on this project. A project takes at most 50 tags, so the include lists them all. `tag_ids` replaces the whole set."
                                    }
                                },
                                "additionalProperties": false,
                                "minProperties": 1
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The updated record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Project"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Put a project on hold",
                        "path": {
                            "project": 1
                        },
                        "body": {
                            "status": "on-hold"
                        },
                        "headers": {
                            "If-Match": "*"
                        }
                    }
                ],
                "x-plnnrly-permission": "projects::update",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:projects"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/ProjectList",
                    "detail": "#/components/schemas/Project"
                }
            },
            "delete": {
                "operationId": "api.v2.projects.destroy",
                "summary": "Delete a project",
                "description": "Soft-deletes a project; `POST actions/restore` clears the marker again. Pass `force=true` to delete permanently. A project with logged time is refused with `409` `has_logged_time`. A soft delete leaves the project's notes, drive items and tags in place, so a restore brings it back intact; only `force=true` removes them.",
                "tags": [
                    "Projects"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:destroy:projects"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "project",
                        "in": "path",
                        "description": "The Project's numeric id. Accepts a soft-deleted record.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/Force"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "responses": {
                    "204": {
                        "description": "Deleted. No body.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "description": "Conflict.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/StateConflictProblem"
                                },
                                "examples": {
                                    "state-conflict": {
                                        "$ref": "#/components/examples/StateConflictProblem"
                                    }
                                }
                            }
                        }
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Permanent delete",
                        "path": {
                            "project": 1
                        },
                        "query": {
                            "force": "true"
                        }
                    }
                ],
                "x-plnnrly-permission": "projects::delete",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:destroy:projects"
                ]
            },
            "x-plnnrly-allow": [
                "GET",
                "PATCH",
                "DELETE"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/projects/{project}/actions/restore": {
            "post": {
                "operationId": "api.v2.projects.actions.restore",
                "summary": "Restore a deleted project",
                "description": "Clears the soft-delete marker and returns the project in the detail view. Restoring additionally requires `projects::delete` as well as `projects::create`. A restore fully undoes a soft delete: a soft delete leaves the project's notes, drive items, worklogs and tags in place, so the project comes back with all of them. A force delete cannot be undone. Restoring a project that is not deleted succeeds and returns an `already_in_target_state` warning, so a retry is safe. The restore finds a deleted project without the trashed parameter and does not accept it.",
                "tags": [
                    "Projects"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:projects"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:projects"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "project",
                        "in": "path",
                        "description": "The Project's numeric id. Accepts a soft-deleted record.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The record after the action ran.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "Idempotency-Replayed": {
                                "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise.",
                                "schema": {
                                    "type": "boolean",
                                    "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Project"
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/ProjectsActionsRestoreMeta"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "$ref": "#/components/responses/Problem409Idempotency"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Restore a deleted project",
                        "path": {
                            "project": 1
                        },
                        "body": {}
                    }
                ],
                "x-plnnrly-permission": "projects::create",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:projects"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/ProjectList",
                    "detail": "#/components/schemas/Project"
                }
            },
            "x-plnnrly-allow": [
                "POST"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/projects/{project}/tasks": {
            "get": {
                "operationId": "api.v2.projects.tasks.index",
                "summary": "List a project's tasks",
                "description": "Returns the kanban tasks on one project, lowest board position first. There is no organisation-wide collection of tasks (a kanban board is per-user), so this nested path is the only way to list them, and `filter[project_id]` / `filter[customer_id]` do not exist: the project is the path segment. Tasks are per-user, so `filter[user_id]` defaults to the authenticated user and naming another user requires `kanban::manage_for_others`; without that permission you also do not see the cards left unassigned in plnnrly. Listing additionally requires `projects::read` on the parent project: the declared `kanban::manage_self` is only part of the requirement. A trashed project answers `404` unless `trashed=with` is passed. The summary and detail views are identical on this resource.",
                "tags": [
                    "Projects"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:kanban"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:kanban"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "project",
                        "in": "path",
                        "description": "The Project's numeric id. Accepts a soft-deleted record.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/PerPage"
                    },
                    {
                        "$ref": "#/components/parameters/Cursor"
                    },
                    {
                        "$ref": "#/components/parameters/ViewSummary"
                    },
                    {
                        "$ref": "#/components/parameters/Trashed"
                    },
                    {
                        "$ref": "#/components/parameters/WithTotal"
                    },
                    {
                        "name": "filter[budget_id]",
                        "in": "query",
                        "description": "Budget the task is filed under. Combine with the project in the path to narrow a board to one budget.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[budget_id][in]",
                        "in": "query",
                        "description": "Budget the task is filed under. Combine with the project in the path to narrow a board to one budget. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[created_at]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][gte]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][lt]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[due_date]",
                        "in": "query",
                        "description": "Day the task is due. `null` reaches the cards left undated in plnnrly.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[due_date][gte]",
                        "in": "query",
                        "description": "Day the task is due. `null` reaches the cards left undated in plnnrly.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[due_date][lte]",
                        "in": "query",
                        "description": "Day the task is due. `null` reaches the cards left undated in plnnrly.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[due_date][null]",
                        "in": "query",
                        "description": "Day the task is due. `null` reaches the cards left undated in plnnrly. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[is_billable]",
                        "in": "query",
                        "description": "Whether time logged on this task is billable.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[is_finished]",
                        "in": "query",
                        "description": "Whether the task is done. Kept in step with `status` automatically, so `filter[is_finished]=true` and `filter[status]=finished` select the same rows.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[status]",
                        "in": "query",
                        "description": "Kanban column the card sits in.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "on-hold",
                                "new",
                                "active",
                                "internal-test",
                                "external-test",
                                "finished"
                            ],
                            "x-extensible-enum": true
                        }
                    },
                    {
                        "name": "filter[status][ne]",
                        "in": "query",
                        "description": "Kanban column the card sits in.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "on-hold",
                                "new",
                                "active",
                                "internal-test",
                                "external-test",
                                "finished"
                            ],
                            "x-extensible-enum": true
                        }
                    },
                    {
                        "name": "filter[status][in]",
                        "in": "query",
                        "description": "Kanban column the card sits in. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "string",
                                "enum": [
                                    "on-hold",
                                    "new",
                                    "active",
                                    "internal-test",
                                    "external-test",
                                    "finished"
                                ],
                                "x-extensible-enum": true
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[team_id]",
                        "in": "query",
                        "description": "Team the task is filed under. Only plnnrly itself assigns one, so tasks created through the API always match `filter[team_id][null]=true`.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[team_id][in]",
                        "in": "query",
                        "description": "Team the task is filed under. Only plnnrly itself assigns one, so tasks created through the API always match `filter[team_id][null]=true`. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[team_id][null]",
                        "in": "query",
                        "description": "Team the task is filed under. Only plnnrly itself assigns one, so tasks created through the API always match `filter[team_id][null]=true`. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[updated_at]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][gte]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][lt]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[user_id]",
                        "in": "query",
                        "description": "Whose board to return. Accepts the literal `me`, and defaults to the authenticated user; naming another user requires `kanban::manage_for_others`.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[user_id][in]",
                        "in": "query",
                        "description": "Whose board to return. Accepts the literal `me`, and defaults to the authenticated user; naming another user requires `kanban::manage_for_others`. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[user_id][null]",
                        "in": "query",
                        "description": "Whose board to return. Accepts the literal `me`, and defaults to the authenticated user; naming another user requires `kanban::manage_for_others`. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/TaskSort"
                    },
                    {
                        "name": "q",
                        "in": "query",
                        "description": "Free-text search over `description`. At most one substring match per request: `q` together with a `contains` filter is 400 invalid-query-parameter.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "minLength": 1
                        }
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "A page of records.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record.",
                                "schema": {
                                    "type": "string",
                                    "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/TaskList"
                                            }
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/ProjectsTasksIndexMeta"
                                        },
                                        "links": {
                                            "$ref": "#/components/schemas/PageLinks"
                                        }
                                    },
                                    "required": [
                                        "data",
                                        "meta",
                                        "links"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "My open cards on one project, soonest due first",
                        "path": {
                            "project": 1
                        },
                        "query": {
                            "filter[is_finished]": "false",
                            "sort": "due_date,order_column"
                        }
                    }
                ],
                "x-plnnrly-permission": "kanban::manage_self",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1,
                    "view_detail_total": 3,
                    "surcharges": {
                        "with_total": 2,
                        "q": 2
                    }
                },
                "x-plnnrly-token-ability": [
                    "api:read:kanban"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/TaskList",
                    "detail": "#/components/schemas/Task"
                }
            },
            "post": {
                "operationId": "api.v2.projects.tasks.store",
                "summary": "Create a task on a project",
                "description": "Creates a kanban task under one of the project's budgets and returns it with a `Location` header pointing at the flat item path. The project comes from the path, so `project_id` is not accepted in the body, and a task has no `project_id` of its own anyway: `budget_id` is the task's only link to a project, it is settable here and nowhere else, and a budget belonging to a different project is rejected as an unprocessable relationship. The task is always created for the authenticated user: `user_id` is not accepted, so creating a card on a colleague's board is not available through the API even to a holder of `kanban::manage_for_others`. Creating additionally requires `projects::read` for the parent project, plus `budgets::read` and a token satisfying `api:read:budgets` for the budget reference; a token created with `api:write:kanban` alone gets a permanent 422 on `budget_id`. A trashed project answers `404` with no escape.",
                "tags": [
                    "Projects"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:kanban"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:kanban"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "project",
                        "in": "path",
                        "description": "The Project's numeric id. Accepts a soft-deleted record.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "budget_id": {
                                        "type": "integer",
                                        "x-plnnrly-references": {
                                            "resource": "Budget",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "budgets",
                                        "description": "Budget the task is filed under. This is the task's only link to a project (a task has no `project_id` of its own), and it is settable at create time only; a task cannot be re-budgeted through the API."
                                    },
                                    "due_date": {
                                        "type": "string",
                                        "format": "date",
                                        "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                                        "description": "Day the task is due. Required when creating a task through the API, and clearable afterwards; tasks created in plnnrly may carry `null`."
                                    },
                                    "minutes": {
                                        "type": "integer",
                                        "minimum": 0,
                                        "description": "Estimated effort, in minutes."
                                    },
                                    "description": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "description": "Short description shown on the kanban card. This is the task's label and the only field `?q=` searches."
                                    },
                                    "comment": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 255,
                                        "description": "Optional longer comment on the card, capped at 255 characters."
                                    },
                                    "status": {
                                        "type": "string",
                                        "enum": [
                                            "on-hold",
                                            "new",
                                            "active",
                                            "internal-test",
                                            "external-test",
                                            "finished"
                                        ],
                                        "x-enum-descriptions": {
                                            "active": "Being worked on.",
                                            "external-test": "With the customer for acceptance.",
                                            "finished": "Done. The only status for which `is_finished` is true.",
                                            "internal-test": "Awaiting internal review or testing.",
                                            "new": "The default for a new card: not picked up yet.",
                                            "on-hold": "Paused, waiting on something off the board."
                                        },
                                        "x-extensible-enum": true,
                                        "description": "Kanban column the card sits in."
                                    },
                                    "is_finished": {
                                        "type": "boolean",
                                        "description": "Whether the task is done. Cross-synced with `status` on every write: setting `status` to finished sets this `true` and any other status sets it `false`, while setting this `false` on a finished task moves `status` back to active. `status` wins when both are sent."
                                    },
                                    "is_billable": {
                                        "type": "boolean",
                                        "description": "Whether time logged on this task is billable. Defaults to `true` when a task is created through the API, regardless of the budget's own setting."
                                    }
                                },
                                "required": [
                                    "budget_id",
                                    "due_date",
                                    "minutes",
                                    "description"
                                ],
                                "additionalProperties": false
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Created.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "Idempotency-Replayed": {
                                "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise.",
                                "schema": {
                                    "type": "boolean",
                                    "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise."
                                }
                            },
                            "Location": {
                                "description": "The absolute URL of the created or queued resource.",
                                "schema": {
                                    "type": "string",
                                    "description": "The absolute URL of the created or queued resource."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Task"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "$ref": "#/components/responses/Problem409Idempotency"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "A card on one of the project's budgets",
                        "path": {
                            "project": 1
                        },
                        "body": {
                            "budget_id": 1,
                            "description": "Wire up the SSO callback",
                            "due_date": "2026-09-15",
                            "minutes": 240
                        }
                    }
                ],
                "x-plnnrly-permission": "kanban::manage_self",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:kanban"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/TaskList",
                    "detail": "#/components/schemas/Task"
                }
            },
            "x-plnnrly-allow": [
                "GET",
                "POST"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/sales-invoice-rows/{sales_invoice_row}": {
            "get": {
                "operationId": "api.v2.sales-invoice-rows.show",
                "summary": "Read an invoice line",
                "description": "Returns one invoice line in the detail view. Lines are soft-deleted, so a deleted line is `404` unless `trashed=with` or `trashed=only` is passed; `trashed=only` 404s a live line. This flat path performs no lookup of the parent invoice: a line is readable by id to any caller holding `sales_invoices::read`, which is what plnnrly does too.",
                "tags": [
                    "Billing"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:sales_invoices"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:sales_invoices"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "sales_invoice_row",
                        "in": "path",
                        "description": "The SalesInvoiceRow's numeric id. Accepts a soft-deleted record.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/Trashed"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/SalesInvoiceRow"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "One line with its invoice expanded",
                        "path": {
                            "sales_invoice_row": 1
                        },
                        "query": {
                            "include": "sales_invoice"
                        }
                    }
                ],
                "x-plnnrly-feature": "billing",
                "x-plnnrly-permission": "sales_invoices::read",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:read:sales_invoices"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/SalesInvoiceRowList",
                    "detail": "#/components/schemas/SalesInvoiceRow"
                }
            },
            "patch": {
                "operationId": "api.v2.sales-invoice-rows.update",
                "summary": "Update an invoice line",
                "description": "Partially updates one line: absent keys are untouched. Send `If-Match` with the `ETag` from your last read to detect a concurrent edit, which answers `412`. `vat_amount` and `total_amount` are server-assigned: changing any of quantity, `unit_price` or `vat_rate` recomputes both, as the invoice form does. The invoice's subtotal, `vat_total` and total are recalculated in the same transaction. A line of a legacy or synced invoice answers `409` `state-conflict`. A caller without `sales_invoices::finalize` also returns the invoice to `pending_approval`, as editing the invoice itself does. `sales_invoice_id` and `sort_order` are not accepted: a line cannot be moved to another invoice, and there is no way to reorder lines through the API.",
                "tags": [
                    "Billing"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:sales_invoices"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:sales_invoices"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "sales_invoice_row",
                        "in": "path",
                        "description": "The SalesInvoiceRow's numeric id. Accepts a soft-deleted record.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/Trashed"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "description": "The line label, as printed on the invoice. Exact Online prints this when description is empty."
                                    },
                                    "quantity": {
                                        "type": "string",
                                        "format": "decimal",
                                        "pattern": "^-?\\d+(\\.\\d{1,2})?$",
                                        "x-plnnrly-constraint": "At least -99999999.99. At most 99999999.99.",
                                        "x-plnnrly-maximum": 99999999.99,
                                        "x-plnnrly-minimum": -99999999.99,
                                        "description": "Number of units billed. Fractional and signed: a budget row carries hours, and a credit line carries a negative quantity."
                                    },
                                    "unit_price": {
                                        "type": "string",
                                        "format": "decimal",
                                        "pattern": "^-?\\d+(\\.\\d{1,2})?$",
                                        "x-plnnrly-constraint": "At least -99999999.99. At most 99999999.99.",
                                        "x-plnnrly-maximum": 99999999.99,
                                        "x-plnnrly-minimum": -99999999.99,
                                        "description": "Price per unit, excluding VAT. This is what actually bills."
                                    },
                                    "description": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "description": "Longer line text. Exact Online prints this in preference to name."
                                    },
                                    "hourly_rate": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "format": "decimal",
                                        "pattern": "^-?\\d+(\\.\\d{1,2})?$",
                                        "description": "Rate plnnrly recomputes `unit_price` from when the aggregated-price toggle is switched on a budget row. Stored exactly as sent and `null` when omitted; it feeds no server-side computation."
                                    },
                                    "billable_minutes": {
                                        "type": "integer",
                                        "minimum": 0,
                                        "description": "Minutes of budget time this line settles. Zero on a hand-typed row."
                                    },
                                    "is_aggregated_price": {
                                        "type": "boolean",
                                        "description": "True: one line of quantity 1 priced at hours times rate. False: quantity carries the hours and `unit_price` the hourly rate."
                                    },
                                    "vat_rate": {
                                        "type": "integer",
                                        "minimum": 0,
                                        "maximum": 100,
                                        "description": "VAT percentage as a whole number: 21 means 21%."
                                    },
                                    "budget_id": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "x-plnnrly-references": {
                                            "resource": "Budget",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "budgets",
                                        "description": "Project budget this invoice line settles, when the row was generated from budget hours."
                                    },
                                    "subscription_item_id": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "x-plnnrly-references": {
                                            "resource": "SubscriptionItem",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "customer_subscriptions",
                                        "description": "Subscription line this invoice line was generated from, when the row came out of a subscription renewal rather than being typed by hand."
                                    },
                                    "exact_item_id": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 255,
                                        "description": "Exact Online article identifier this line bills against. Empty is accepted, but a row without it fails the whole invoice's Exact sync."
                                    },
                                    "exact_vat_code": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 255,
                                        "description": "Exact Online VAT code, by code rather than by id."
                                    },
                                    "exact_cost_center": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 255,
                                        "description": "Exact Online cost-centre code."
                                    },
                                    "exact_gl_account": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 255,
                                        "description": "Exact Online general-ledger revenue account for this line."
                                    }
                                },
                                "additionalProperties": false,
                                "minProperties": 1
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The updated record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/SalesInvoiceRow"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "description": "Conflict.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/StateConflictProblem"
                                },
                                "examples": {
                                    "state-conflict": {
                                        "$ref": "#/components/examples/StateConflictProblem"
                                    }
                                }
                            }
                        }
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Correct the billed hours",
                        "path": {
                            "sales_invoice_row": 1
                        },
                        "body": {
                            "quantity": "14.00"
                        },
                        "headers": {
                            "If-Match": "*"
                        }
                    }
                ],
                "x-plnnrly-feature": "billing",
                "x-plnnrly-permission": "sales_invoices::update",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:sales_invoices"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/SalesInvoiceRowList",
                    "detail": "#/components/schemas/SalesInvoiceRow"
                }
            },
            "delete": {
                "operationId": "api.v2.sales-invoice-rows.destroy",
                "summary": "Delete an invoice line",
                "description": "Soft-deletes one line and recalculates the invoice's subtotal, `vat_total` and total in the same transaction. There is no permanent delete and no restore verb: a line comes back by being re-created through `POST sales-invoices/{sales_invoice}/rows`, or by restoring the invoice it belonged to. Because this is a soft delete, worklogs settled against the line keep pointing at it: they are neither shown as re-invoiceable nor released, and re-creating the line does not re-couple them. Only deleting the whole invoice releases its hours. Re-deleting an already-deleted line answers `204`, so a retry is safe. A line of a legacy or synced invoice answers `409` `state-conflict`. A caller without `sales_invoices::finalize` also returns the invoice to `pending_approval`, as editing the invoice itself does.",
                "tags": [
                    "Billing"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:destroy:sales_invoices"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "sales_invoice_row",
                        "in": "path",
                        "description": "The SalesInvoiceRow's numeric id. Accepts a soft-deleted record.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "responses": {
                    "204": {
                        "description": "Deleted. No body.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "description": "Conflict.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/StateConflictProblem"
                                },
                                "examples": {
                                    "state-conflict": {
                                        "$ref": "#/components/examples/StateConflictProblem"
                                    }
                                }
                            }
                        }
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Remove a line",
                        "path": {
                            "sales_invoice_row": 1
                        }
                    }
                ],
                "x-plnnrly-feature": "billing",
                "x-plnnrly-permission": "sales_invoices::delete",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:destroy:sales_invoices"
                ]
            },
            "x-plnnrly-allow": [
                "GET",
                "PATCH",
                "DELETE"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/sales-invoices": {
            "get": {
                "operationId": "api.v2.sales-invoices.index",
                "summary": "List sales invoices",
                "description": "Returns the organisation's sales invoices, newest invoice date first. Defaults to the summary view; `view=detail` adds `sync_error` and `internal_notes` and costs three read units instead of one. Search with `q=` over the invoice number, and pass `trashed=with|only` to see deleted invoices. Combine `filter[invoice_date][gte]` and `[lte]` for a period, `filter[status]=draft` or `pending_approval` for the concept and approval queues in plnnrly, and `filter[sync_status]=failed` for invoices the accounting sync rejected. Sorting by total or invoice number is deliberately not offered, so a money-ordered page has to be sorted client-side.",
                "tags": [
                    "Billing"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:sales_invoices"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:sales_invoices"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/PerPage"
                    },
                    {
                        "$ref": "#/components/parameters/Cursor"
                    },
                    {
                        "$ref": "#/components/parameters/PageNumber"
                    },
                    {
                        "$ref": "#/components/parameters/ViewSummary"
                    },
                    {
                        "$ref": "#/components/parameters/Trashed"
                    },
                    {
                        "name": "filter[created_at]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][gte]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][lt]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[customer_id]",
                        "in": "query",
                        "description": "Customer the invoice is billed to.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[customer_id][in]",
                        "in": "query",
                        "description": "Customer the invoice is billed to. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[customer_subscription_id]",
                        "in": "query",
                        "description": "Customer subscription the invoice was generated from. Pass `[null]` for invoices that were not generated from a renewal.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[customer_subscription_id][in]",
                        "in": "query",
                        "description": "Customer subscription the invoice was generated from. Pass `[null]` for invoices that were not generated from a renewal. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[customer_subscription_id][null]",
                        "in": "query",
                        "description": "Customer subscription the invoice was generated from. Pass `[null]` for invoices that were not generated from a renewal. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[due_date]",
                        "in": "query",
                        "description": "Date payment is due. Pass `[null]` for invoices with no term applied.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[due_date][gte]",
                        "in": "query",
                        "description": "Date payment is due. Pass `[null]` for invoices with no term applied.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[due_date][lte]",
                        "in": "query",
                        "description": "Date payment is due. Pass `[null]` for invoices with no term applied.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[due_date][null]",
                        "in": "query",
                        "description": "Date payment is due. Pass `[null]` for invoices with no term applied. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[invoice_date]",
                        "in": "query",
                        "description": "Invoice date, a zone-free calendar date. Combine `[gte]` and `[lte]` for a period.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[invoice_date][gte]",
                        "in": "query",
                        "description": "Invoice date, a zone-free calendar date. Combine `[gte]` and `[lte]` for a period.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[invoice_date][lte]",
                        "in": "query",
                        "description": "Invoice date, a zone-free calendar date. Combine `[gte]` and `[lte]` for a period.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[is_legacy]",
                        "in": "query",
                        "description": "Whether the invoice was converted from a legacy invoice, and is therefore read-only.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[legacy_invoice_id]",
                        "in": "query",
                        "description": "The id of the legacy invoice this invoice was converted from, so it can be matched with `GET /api/v2/invoices`.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[legacy_invoice_id][in]",
                        "in": "query",
                        "description": "The id of the legacy invoice this invoice was converted from, so it can be matched with `GET /api/v2/invoices`. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[legacy_invoice_id][null]",
                        "in": "query",
                        "description": "The id of the legacy invoice this invoice was converted from, so it can be matched with `GET /api/v2/invoices`. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[paid_at]",
                        "in": "query",
                        "description": "When the invoice was settled. Pass `[null]` for unsettled invoices.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[paid_at][gte]",
                        "in": "query",
                        "description": "When the invoice was settled. Pass `[null]` for unsettled invoices.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[paid_at][lte]",
                        "in": "query",
                        "description": "When the invoice was settled. Pass `[null]` for unsettled invoices.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[paid_at][null]",
                        "in": "query",
                        "description": "When the invoice was settled. Pass `[null]` for unsettled invoices. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[project_id]",
                        "in": "query",
                        "description": "Project the invoice was raised for. Pass `[null]` for invoices raised against no project.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[project_id][in]",
                        "in": "query",
                        "description": "Project the invoice was raised for. Pass `[null]` for invoices raised against no project. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[project_id][null]",
                        "in": "query",
                        "description": "Project the invoice was raised for. Pass `[null]` for invoices raised against no project. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[should_email]",
                        "in": "query",
                        "description": "Whether the invoice is emailed to the customer when it syncs.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[status]",
                        "in": "query",
                        "description": "Lifecycle status of the invoice.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "draft",
                                "pending_approval",
                                "sent",
                                "paid",
                                "overdue",
                                "cancelled"
                            ],
                            "x-extensible-enum": true
                        }
                    },
                    {
                        "name": "filter[status][ne]",
                        "in": "query",
                        "description": "Lifecycle status of the invoice.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "draft",
                                "pending_approval",
                                "sent",
                                "paid",
                                "overdue",
                                "cancelled"
                            ],
                            "x-extensible-enum": true
                        }
                    },
                    {
                        "name": "filter[status][in]",
                        "in": "query",
                        "description": "Lifecycle status of the invoice. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "string",
                                "enum": [
                                    "draft",
                                    "pending_approval",
                                    "sent",
                                    "paid",
                                    "overdue",
                                    "cancelled"
                                ],
                                "x-extensible-enum": true
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[sync_attempted_at]",
                        "in": "query",
                        "description": "When the invoice was last handed to the sync, successfully or not.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[sync_attempted_at][gte]",
                        "in": "query",
                        "description": "When the invoice was last handed to the sync, successfully or not.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[sync_attempted_at][lte]",
                        "in": "query",
                        "description": "When the invoice was last handed to the sync, successfully or not.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[sync_attempted_at][null]",
                        "in": "query",
                        "description": "When the invoice was last handed to the sync, successfully or not. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[sync_status]",
                        "in": "query",
                        "description": "How far the invoice has got through the accounting sync. `filter[sync_status]=failed` reproduces the sync error page in plnnrly.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "pending",
                                "synced",
                                "failed",
                                "local"
                            ],
                            "x-extensible-enum": true
                        }
                    },
                    {
                        "name": "filter[sync_status][ne]",
                        "in": "query",
                        "description": "How far the invoice has got through the accounting sync. `filter[sync_status]=failed` reproduces the sync error page in plnnrly.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "pending",
                                "synced",
                                "failed",
                                "local"
                            ],
                            "x-extensible-enum": true
                        }
                    },
                    {
                        "name": "filter[sync_status][in]",
                        "in": "query",
                        "description": "How far the invoice has got through the accounting sync. `filter[sync_status]=failed` reproduces the sync error page in plnnrly. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "string",
                                "enum": [
                                    "pending",
                                    "synced",
                                    "failed",
                                    "local"
                                ],
                                "x-extensible-enum": true
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[synced_at]",
                        "in": "query",
                        "description": "When the invoice last synced successfully. Pass `[null]` for invoices that have never synced.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[synced_at][gte]",
                        "in": "query",
                        "description": "When the invoice last synced successfully. Pass `[null]` for invoices that have never synced.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[synced_at][lte]",
                        "in": "query",
                        "description": "When the invoice last synced successfully. Pass `[null]` for invoices that have never synced.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[synced_at][null]",
                        "in": "query",
                        "description": "When the invoice last synced successfully. Pass `[null]` for invoices that have never synced. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[updated_at]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][gte]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][lt]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/SalesInvoiceSort"
                    },
                    {
                        "name": "q",
                        "in": "query",
                        "description": "Free-text search over `invoice_number`. At most one substring match per request: `q` together with a `contains` filter is 400 invalid-query-parameter.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "minLength": 1
                        }
                    },
                    {
                        "$ref": "#/components/parameters/SalesInvoiceInclude"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "A page of records.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record.",
                                "schema": {
                                    "type": "string",
                                    "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/SalesInvoiceList"
                                            }
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/SalesInvoicesIndexMeta"
                                        },
                                        "links": {
                                            "$ref": "#/components/schemas/PageLinks"
                                        }
                                    },
                                    "required": [
                                        "data",
                                        "meta",
                                        "links"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Unsynced invoices for one customer this quarter",
                        "query": {
                            "filter[customer_id]": 1,
                            "filter[invoice_date][gte]": "2026-01-01",
                            "filter[sync_status]": "pending",
                            "sort": "-invoice_date"
                        }
                    }
                ],
                "x-plnnrly-feature": "billing",
                "x-plnnrly-permission": "sales_invoices::read",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1,
                    "view_detail_total": 3,
                    "surcharges": {
                        "include": 1,
                        "q": 2
                    }
                },
                "x-plnnrly-token-ability": [
                    "api:read:sales_invoices"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/SalesInvoiceList",
                    "detail": "#/components/schemas/SalesInvoice"
                }
            },
            "post": {
                "operationId": "api.v2.sales-invoices.store",
                "summary": "Create a sales invoice",
                "description": "Creates an invoice header and returns it in the detail view with a `Location` header. It has no rows yet, so subtotal, `vat_total` and total are 0.00: post each row to `sales-invoices/{sales_invoice}/rows` afterwards and the totals follow. The invoice number is allocated by the server from the organisation's yearly sequence and cannot be supplied. The server sets the status too: a caller without `sales_invoices::finalize` creates a `pending_approval` invoice that a colleague holding that permission must approve in plnnrly; no API endpoint advances it. In an organisation with the accounting integration connected the invoice starts as a draft awaiting the sync, and the customer must be linked to that system or the sync will later fail; in one without it the invoice is local, immediately settled, and never emailed. Send `Idempotency-Key` to make a retry safe.",
                "tags": [
                    "Billing"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:sales_invoices"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:sales_invoices"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "customer_id": {
                                        "type": "integer",
                                        "x-plnnrly-references": {
                                            "resource": "Customer",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "customers",
                                        "description": "Customer this invoice is billed to."
                                    },
                                    "project_id": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "x-plnnrly-references": {
                                            "resource": "Project",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "projects",
                                        "description": "Project this invoice was raised for, when it was raised from one."
                                    },
                                    "customer_subscription_id": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "x-plnnrly-references": {
                                            "resource": "CustomerSubscription",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "customer_subscriptions",
                                        "description": "Customer subscription this invoice was generated from, when it was generated from a renewal."
                                    },
                                    "invoice_date": {
                                        "type": "string",
                                        "format": "date",
                                        "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                                        "description": "Invoice date, a zone-free calendar date. Newest first is the default sort."
                                    },
                                    "due_date": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "format": "date",
                                        "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                                        "x-plnnrly-constraint": "Must be on or after invoice_date.",
                                        "description": "Date payment is due, a zone-free calendar date. Null when no term was applied."
                                    },
                                    "reference": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 255,
                                        "description": "The customer's own reference, typically a purchase-order number, reproduced on the invoice."
                                    },
                                    "description": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 255,
                                        "description": "One-line description of what was invoiced."
                                    },
                                    "internal_notes": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "description": "Notes for colleagues. Never rendered on the invoice the customer receives."
                                    },
                                    "should_email": {
                                        "type": "boolean",
                                        "description": "Whether the invoice is emailed to the customer when it syncs. The email is sent through the accounting integration, so it is forced to `false` in an organisation with no integration configured. Writing it requires `sales_invoices::finalize`."
                                    }
                                },
                                "required": [
                                    "customer_id",
                                    "invoice_date"
                                ],
                                "additionalProperties": false
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Created.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "Idempotency-Replayed": {
                                "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise.",
                                "schema": {
                                    "type": "boolean",
                                    "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise."
                                }
                            },
                            "Location": {
                                "description": "The absolute URL of the created or queued resource.",
                                "schema": {
                                    "type": "string",
                                    "description": "The absolute URL of the created or queued resource."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/SalesInvoice"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "$ref": "#/components/responses/Problem409Idempotency"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "An invoice for one customer",
                        "body": {
                            "customer_id": 1,
                            "invoice_date": "2026-02-01",
                            "due_date": "2026-03-03",
                            "reference": "PO-4471",
                            "description": "Development work, January 2026"
                        }
                    }
                ],
                "x-plnnrly-feature": "billing",
                "x-plnnrly-permission": "sales_invoices::create",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:sales_invoices"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/SalesInvoiceList",
                    "detail": "#/components/schemas/SalesInvoice"
                }
            },
            "x-plnnrly-allow": [
                "GET",
                "POST"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/sales-invoices/{sales_invoice}": {
            "get": {
                "operationId": "api.v2.sales-invoices.show",
                "summary": "Read a sales invoice",
                "description": "Returns one invoice in the detail view. It is addressable by numeric id and by invoice number on the same path, so `sales-invoices/B-26-00001` and `sales-invoices/L-1234` both resolve. Invoices are soft-deleted, so a deleted invoice is `404` unless `trashed=with` or `trashed=only` is passed; `trashed=only` 404s a live one. The line rows are not part of this payload: `rows_count` reports how many there are, and `links.rows` is where they live.",
                "tags": [
                    "Billing"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:sales_invoices"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:sales_invoices"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "sales_invoice",
                        "in": "path",
                        "description": "The SalesInvoice's numeric id. Or its invoice_number, matching `[A-Z]+-[0-9]{2}-[0-9]{5}|L-[0-9]+`. Accepts a soft-deleted record.",
                        "required": true,
                        "schema": {
                            "oneOf": [
                                {
                                    "type": "integer"
                                },
                                {
                                    "type": "string",
                                    "pattern": "[A-Z]+-[0-9]{2}-[0-9]{5}|L-[0-9]+"
                                }
                            ]
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/Trashed"
                    },
                    {
                        "$ref": "#/components/parameters/Meta"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/SalesInvoice"
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/SalesInvoicesShowMeta"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "One invoice with its customer",
                        "path": {
                            "sales_invoice": 1
                        },
                        "query": {
                            "include": "customer"
                        }
                    }
                ],
                "x-plnnrly-feature": "billing",
                "x-plnnrly-permission": "sales_invoices::read",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:read:sales_invoices"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/SalesInvoiceList",
                    "detail": "#/components/schemas/SalesInvoice"
                }
            },
            "patch": {
                "operationId": "api.v2.sales-invoices.update",
                "summary": "Update a sales invoice",
                "description": "Partially updates the invoice header: absent keys are untouched, and the line rows are edited through their own endpoints. Send `If-Match` with the `ETag` from your last read to detect a concurrent edit, which answers `412`. A legacy invoice cannot be changed at all and answers `409` `state-conflict` with reason `locked_by_legacy`. An invoice that has already synced to the accounting system can still be edited here, which goes further than plnnrly: plnnrly closes its edit form once an invoice is synced, and the change will not be pushed to the accounting system again. status is never writable: it is stripped from the payload, and any update by a caller without `sales_invoices::finalize` additionally returns the invoice to `pending_approval`, so an already-approved invoice cannot be changed and synced without a finalizer re-checking it.",
                "tags": [
                    "Billing"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:sales_invoices"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:sales_invoices"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "sales_invoice",
                        "in": "path",
                        "description": "The SalesInvoice's numeric id. Or its invoice_number, matching `[A-Z]+-[0-9]{2}-[0-9]{5}|L-[0-9]+`. Accepts a soft-deleted record.",
                        "required": true,
                        "schema": {
                            "oneOf": [
                                {
                                    "type": "integer"
                                },
                                {
                                    "type": "string",
                                    "pattern": "[A-Z]+-[0-9]{2}-[0-9]{5}|L-[0-9]+"
                                }
                            ]
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/Trashed"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "customer_id": {
                                        "type": "integer",
                                        "x-plnnrly-references": {
                                            "resource": "Customer",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "customers",
                                        "description": "Customer this invoice is billed to."
                                    },
                                    "project_id": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "x-plnnrly-references": {
                                            "resource": "Project",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "projects",
                                        "description": "Project this invoice was raised for, when it was raised from one."
                                    },
                                    "customer_subscription_id": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "x-plnnrly-references": {
                                            "resource": "CustomerSubscription",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "customer_subscriptions",
                                        "description": "Customer subscription this invoice was generated from, when it was generated from a renewal."
                                    },
                                    "invoice_date": {
                                        "type": "string",
                                        "format": "date",
                                        "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                                        "description": "Invoice date, a zone-free calendar date. Newest first is the default sort."
                                    },
                                    "due_date": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "format": "date",
                                        "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                                        "x-plnnrly-constraint": "Must be on or after invoice_date.",
                                        "description": "Date payment is due, a zone-free calendar date. Null when no term was applied."
                                    },
                                    "reference": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 255,
                                        "description": "The customer's own reference, typically a purchase-order number, reproduced on the invoice."
                                    },
                                    "description": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 255,
                                        "description": "One-line description of what was invoiced."
                                    },
                                    "internal_notes": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "description": "Notes for colleagues. Never rendered on the invoice the customer receives."
                                    },
                                    "should_email": {
                                        "type": "boolean",
                                        "description": "Whether the invoice is emailed to the customer when it syncs. The email is sent through the accounting integration, so it is forced to `false` in an organisation with no integration configured. Writing it requires `sales_invoices::finalize`."
                                    }
                                },
                                "additionalProperties": false,
                                "minProperties": 1
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The updated record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/SalesInvoice"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "description": "Conflict.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/StateConflictProblem"
                                },
                                "examples": {
                                    "state-conflict": {
                                        "$ref": "#/components/examples/StateConflictProblem"
                                    }
                                }
                            }
                        }
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Move the due date out",
                        "path": {
                            "sales_invoice": 1
                        },
                        "body": {
                            "due_date": "2026-03-31"
                        },
                        "headers": {
                            "If-Match": "*"
                        }
                    }
                ],
                "x-plnnrly-feature": "billing",
                "x-plnnrly-permission": "sales_invoices::update",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:sales_invoices"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/SalesInvoiceList",
                    "detail": "#/components/schemas/SalesInvoice"
                }
            },
            "delete": {
                "operationId": "api.v2.sales-invoices.destroy",
                "summary": "Delete a sales invoice",
                "description": "Soft-deletes an invoice: it is only marked deleted, and POST `sales-invoices/{sales_invoice}/actions/restore` reverses that. There is no permanent delete. A legacy invoice answers `409` `state-conflict` with reason `locked_by_legacy`, and an invoice that has already synced to the accounting system answers `409` with reason `locked_by_sync`: deleting it locally would leave the two systems silently out of step. Deleting an invoice releases its hours: every worklog settled against one of its rows is unlinked and returns to the uninvoiced pool, and restoring the invoice brings back its rows but not those links, so an integrator treating restore as an undo will invoice those hours twice. Deleting an already-deleted invoice answers `204`, so a retry is safe.",
                "tags": [
                    "Billing"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:destroy:sales_invoices"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "sales_invoice",
                        "in": "path",
                        "description": "The SalesInvoice's numeric id. Or its invoice_number, matching `[A-Z]+-[0-9]{2}-[0-9]{5}|L-[0-9]+`. Accepts a soft-deleted record.",
                        "required": true,
                        "schema": {
                            "oneOf": [
                                {
                                    "type": "integer"
                                },
                                {
                                    "type": "string",
                                    "pattern": "[A-Z]+-[0-9]{2}-[0-9]{5}|L-[0-9]+"
                                }
                            ]
                        }
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "responses": {
                    "204": {
                        "description": "Deleted. No body.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "description": "Conflict.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/StateConflictProblem"
                                },
                                "examples": {
                                    "state-conflict": {
                                        "$ref": "#/components/examples/StateConflictProblem"
                                    }
                                }
                            }
                        }
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Delete an invoice",
                        "path": {
                            "sales_invoice": 1
                        }
                    }
                ],
                "x-plnnrly-feature": "billing",
                "x-plnnrly-permission": "sales_invoices::delete",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:destroy:sales_invoices"
                ]
            },
            "x-plnnrly-allow": [
                "GET",
                "PATCH",
                "DELETE"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/sales-invoices/{sales_invoice}/actions/restore": {
            "post": {
                "operationId": "api.v2.sales-invoices.actions.restore",
                "summary": "Restore a deleted sales invoice",
                "description": "Clears the soft-delete marker and returns the invoice in the detail view. Restoring additionally requires `sales_invoices::delete` as well as `sales_invoices::create`. Restoring is not a full undo: deleting the invoice unlinked every worklog that had been settled against its rows and returned those hours to the uninvoiced pool, and restoring brings back the invoice and its rows but not those links, so the hours can be invoiced a second time unless you check them. Restoring an invoice that is not deleted succeeds and returns an `already_in_target_state` warning. The restore finds a deleted invoice on its own, so the trashed parameter is not accepted.",
                "tags": [
                    "Billing"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:sales_invoices"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:sales_invoices"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "sales_invoice",
                        "in": "path",
                        "description": "The SalesInvoice's numeric id. Or its invoice_number, matching `[A-Z]+-[0-9]{2}-[0-9]{5}|L-[0-9]+`. Accepts a soft-deleted record.",
                        "required": true,
                        "schema": {
                            "oneOf": [
                                {
                                    "type": "integer"
                                },
                                {
                                    "type": "string",
                                    "pattern": "[A-Z]+-[0-9]{2}-[0-9]{5}|L-[0-9]+"
                                }
                            ]
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The record after the action ran.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "Idempotency-Replayed": {
                                "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise.",
                                "schema": {
                                    "type": "boolean",
                                    "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/SalesInvoice"
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/SalesInvoicesActionsRestoreMeta"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "$ref": "#/components/responses/Problem409Idempotency"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Restore a deleted invoice",
                        "path": {
                            "sales_invoice": 1
                        },
                        "body": {}
                    }
                ],
                "x-plnnrly-feature": "billing",
                "x-plnnrly-permission": "sales_invoices::create",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:sales_invoices"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/SalesInvoiceList",
                    "detail": "#/components/schemas/SalesInvoice"
                }
            },
            "x-plnnrly-allow": [
                "POST"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/sales-invoices/{sales_invoice}/rows": {
            "get": {
                "operationId": "api.v2.sales-invoices.rows.index",
                "summary": "List an invoice's lines",
                "description": "Returns one sales invoice's lines in their printed order, `sort_order` ascending. There is no organisation-wide collection of invoice lines: a line only means anything under the invoice it belongs to, so this nested path is the only way to list them and `filter[sales_invoice_id]` does not exist. A deleted invoice answers `404` for the whole path unless `trashed=with` is passed, which also includes the invoice's deleted lines. Defaults to the summary view; pass `include=sales_invoice` to expand the invoice each line points at.",
                "tags": [
                    "Billing"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:sales_invoices"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:sales_invoices"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "sales_invoice",
                        "in": "path",
                        "description": "The SalesInvoice's numeric id. Or its invoice_number, matching `[A-Z]+-[0-9]{2}-[0-9]{5}|L-[0-9]+`. Accepts a soft-deleted record.",
                        "required": true,
                        "schema": {
                            "oneOf": [
                                {
                                    "type": "integer"
                                },
                                {
                                    "type": "string",
                                    "pattern": "[A-Z]+-[0-9]{2}-[0-9]{5}|L-[0-9]+"
                                }
                            ]
                        }
                    },
                    {
                        "$ref": "#/components/parameters/PerPage"
                    },
                    {
                        "$ref": "#/components/parameters/Cursor"
                    },
                    {
                        "$ref": "#/components/parameters/SalesInvoiceRowPageNumber"
                    },
                    {
                        "$ref": "#/components/parameters/ViewSummary"
                    },
                    {
                        "$ref": "#/components/parameters/Trashed"
                    },
                    {
                        "name": "filter[budget_id]",
                        "in": "query",
                        "description": "Project budget this invoice line settles.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[budget_id][in]",
                        "in": "query",
                        "description": "Project budget this invoice line settles. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[budget_id][null]",
                        "in": "query",
                        "description": "Project budget this invoice line settles. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[created_at]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][gte]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][lt]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[is_aggregated_price]",
                        "in": "query",
                        "description": "Whether the line carries one aggregated price rather than hours times an hourly rate.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[subscription_item_id]",
                        "in": "query",
                        "description": "Subscription line this invoice line was generated from.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[subscription_item_id][in]",
                        "in": "query",
                        "description": "Subscription line this invoice line was generated from. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[subscription_item_id][null]",
                        "in": "query",
                        "description": "Subscription line this invoice line was generated from. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[updated_at]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][gte]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][lt]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/SalesInvoiceRowSort"
                    },
                    {
                        "name": "q",
                        "in": "query",
                        "description": "Free-text search over `name`. At most one substring match per request: `q` together with a `contains` filter is 400 invalid-query-parameter.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "minLength": 1
                        }
                    },
                    {
                        "$ref": "#/components/parameters/SalesInvoiceRowInclude"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "A page of records.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record.",
                                "schema": {
                                    "type": "string",
                                    "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/SalesInvoiceRowList"
                                            }
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/SalesInvoicesRowsIndexMeta"
                                        },
                                        "links": {
                                            "$ref": "#/components/schemas/PageLinks"
                                        }
                                    },
                                    "required": [
                                        "data",
                                        "meta",
                                        "links"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "One invoice's lines, including its deleted ones",
                        "path": {
                            "sales_invoice": 1
                        },
                        "query": {
                            "trashed": "with"
                        }
                    }
                ],
                "x-plnnrly-feature": "billing",
                "x-plnnrly-permission": "sales_invoices::read",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1,
                    "view_detail_total": 3,
                    "surcharges": {
                        "include": 1,
                        "q": 2
                    }
                },
                "x-plnnrly-token-ability": [
                    "api:read:sales_invoices"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/SalesInvoiceRowList",
                    "detail": "#/components/schemas/SalesInvoiceRow"
                }
            },
            "post": {
                "operationId": "api.v2.sales-invoices.rows.store",
                "summary": "Add a line to an invoice",
                "description": "Creates one line on the invoice named in the path and returns it in the detail view, with a `Location` header pointing at the flat item path. The invoice comes from the path, so `sales_invoice_id` is not accepted in the body and a line can never be moved between invoices. `vat_rate` defaults to the organisation's configured VAT percentage. `vat_amount` and `total_amount` are server-assigned, always computed from quantity, `unit_price` and `vat_rate`; `total_amount` excludes VAT. The invoice's own subtotal, `vat_total` and total are recalculated from all of its lines in the same transaction. A deleted invoice answers `404`, and a legacy or synced invoice answers `409` `state-conflict`. A caller without `sales_invoices::finalize` also returns the invoice to `pending_approval`, as editing the invoice itself does. `exact_item_id` is optional here as it is in plnnrly, but in an organisation using Exact a line without it fails the whole invoice's sync, and naming an `exact_item_id` does not auto-fill the VAT code, GL account or hourly rate plnnrly would fill in from it.",
                "tags": [
                    "Billing"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:sales_invoices"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:sales_invoices"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "sales_invoice",
                        "in": "path",
                        "description": "The SalesInvoice's numeric id. Or its invoice_number, matching `[A-Z]+-[0-9]{2}-[0-9]{5}|L-[0-9]+`. Accepts a soft-deleted record.",
                        "required": true,
                        "schema": {
                            "oneOf": [
                                {
                                    "type": "integer"
                                },
                                {
                                    "type": "string",
                                    "pattern": "[A-Z]+-[0-9]{2}-[0-9]{5}|L-[0-9]+"
                                }
                            ]
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "description": "The line label, as printed on the invoice. Exact Online prints this when description is empty."
                                    },
                                    "quantity": {
                                        "type": "string",
                                        "format": "decimal",
                                        "pattern": "^-?\\d+(\\.\\d{1,2})?$",
                                        "x-plnnrly-constraint": "At least -99999999.99. At most 99999999.99.",
                                        "x-plnnrly-maximum": 99999999.99,
                                        "x-plnnrly-minimum": -99999999.99,
                                        "description": "Number of units billed. Fractional and signed: a budget row carries hours, and a credit line carries a negative quantity."
                                    },
                                    "unit_price": {
                                        "type": "string",
                                        "format": "decimal",
                                        "pattern": "^-?\\d+(\\.\\d{1,2})?$",
                                        "x-plnnrly-constraint": "At least -99999999.99. At most 99999999.99.",
                                        "x-plnnrly-maximum": 99999999.99,
                                        "x-plnnrly-minimum": -99999999.99,
                                        "description": "Price per unit, excluding VAT. This is what actually bills."
                                    },
                                    "description": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "description": "Longer line text. Exact Online prints this in preference to name."
                                    },
                                    "hourly_rate": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "format": "decimal",
                                        "pattern": "^-?\\d+(\\.\\d{1,2})?$",
                                        "description": "Rate plnnrly recomputes `unit_price` from when the aggregated-price toggle is switched on a budget row. Stored exactly as sent and `null` when omitted; it feeds no server-side computation."
                                    },
                                    "billable_minutes": {
                                        "type": "integer",
                                        "minimum": 0,
                                        "description": "Minutes of budget time this line settles. Zero on a hand-typed row."
                                    },
                                    "is_aggregated_price": {
                                        "type": "boolean",
                                        "description": "True: one line of quantity 1 priced at hours times rate. False: quantity carries the hours and `unit_price` the hourly rate."
                                    },
                                    "vat_rate": {
                                        "type": "integer",
                                        "minimum": 0,
                                        "maximum": 100,
                                        "description": "VAT percentage as a whole number: 21 means 21%."
                                    },
                                    "budget_id": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "x-plnnrly-references": {
                                            "resource": "Budget",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "budgets",
                                        "description": "Project budget this invoice line settles, when the row was generated from budget hours."
                                    },
                                    "subscription_item_id": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "x-plnnrly-references": {
                                            "resource": "SubscriptionItem",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "customer_subscriptions",
                                        "description": "Subscription line this invoice line was generated from, when the row came out of a subscription renewal rather than being typed by hand."
                                    },
                                    "exact_item_id": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 255,
                                        "description": "Exact Online article identifier this line bills against. Empty is accepted, but a row without it fails the whole invoice's Exact sync."
                                    },
                                    "exact_vat_code": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 255,
                                        "description": "Exact Online VAT code, by code rather than by id."
                                    },
                                    "exact_cost_center": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 255,
                                        "description": "Exact Online cost-centre code."
                                    },
                                    "exact_gl_account": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 255,
                                        "description": "Exact Online general-ledger revenue account for this line."
                                    }
                                },
                                "required": [
                                    "name",
                                    "quantity",
                                    "unit_price"
                                ],
                                "additionalProperties": false
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Created.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "Idempotency-Replayed": {
                                "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise.",
                                "schema": {
                                    "type": "boolean",
                                    "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise."
                                }
                            },
                            "Location": {
                                "description": "The absolute URL of the created or queued resource.",
                                "schema": {
                                    "type": "string",
                                    "description": "The absolute URL of the created or queued resource."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/SalesInvoiceRow"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "description": "Conflict.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "oneOf": [
                                        {
                                            "$ref": "#/components/schemas/IdempotencyKeyInFlightProblem"
                                        },
                                        {
                                            "$ref": "#/components/schemas/IdempotencyKeyReusedProblem"
                                        },
                                        {
                                            "$ref": "#/components/schemas/StateConflictProblem"
                                        }
                                    ],
                                    "discriminator": {
                                        "propertyName": "type",
                                        "mapping": {
                                            "https://developers.plnnrly.com/api/v2/problems/idempotency-key-in-flight": "#/components/schemas/IdempotencyKeyInFlightProblem",
                                            "https://developers.plnnrly.com/api/v2/problems/idempotency-key-reused": "#/components/schemas/IdempotencyKeyReusedProblem",
                                            "https://developers.plnnrly.com/api/v2/problems/state-conflict": "#/components/schemas/StateConflictProblem"
                                        }
                                    }
                                },
                                "examples": {
                                    "idempotency-key-in-flight": {
                                        "$ref": "#/components/examples/IdempotencyKeyInFlightProblem"
                                    },
                                    "idempotency-key-reused": {
                                        "$ref": "#/components/examples/IdempotencyKeyReusedProblem"
                                    },
                                    "state-conflict": {
                                        "$ref": "#/components/examples/StateConflictProblem"
                                    }
                                }
                            }
                        }
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "A hand-typed line at the organisation's default VAT rate",
                        "path": {
                            "sales_invoice": 1
                        },
                        "body": {
                            "name": "Sprint 42: development",
                            "description": "Feature work on the customer portal.",
                            "quantity": "12.50",
                            "unit_price": "95.00"
                        }
                    }
                ],
                "x-plnnrly-feature": "billing",
                "x-plnnrly-permission": "sales_invoices::create",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:sales_invoices"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/SalesInvoiceRowList",
                    "detail": "#/components/schemas/SalesInvoiceRow"
                }
            },
            "x-plnnrly-allow": [
                "GET",
                "POST"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/sprints": {
            "get": {
                "operationId": "api.v2.sprints.index",
                "summary": "List sprints",
                "description": "Returns the organisation's sprints in each project's display order, with the id as the tiebreaker. The list covers the whole organisation and interleaves projects, because `order` is unique within a project only; narrow it with `filter[project_id]`. A sprint's state is derived rather than stored, so there is no `filter[state]`; compose it instead: `filter[is_active]=true&filter[completed_at][null]=true` is the running sprint, `filter[is_active]=false&filter[completed_at][null]=true` the upcoming ones, and `filter[completed_at][null]=false` the history. Listing additionally requires `project_management::manage_sprints`.",
                "tags": [
                    "Sprints"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:project_management"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:project_management"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/PerPage"
                    },
                    {
                        "$ref": "#/components/parameters/Cursor"
                    },
                    {
                        "$ref": "#/components/parameters/SprintPageNumber"
                    },
                    {
                        "$ref": "#/components/parameters/ViewSummary"
                    },
                    {
                        "name": "filter[completed_at]",
                        "in": "query",
                        "description": "When the sprint was actually completed. `filter[completed_at][null]=false` is every completed sprint and `filter[completed_at][null]=true` every sprint still open.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[completed_at][gte]",
                        "in": "query",
                        "description": "When the sprint was actually completed. `filter[completed_at][null]=false` is every completed sprint and `filter[completed_at][null]=true` every sprint still open.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[completed_at][lte]",
                        "in": "query",
                        "description": "When the sprint was actually completed. `filter[completed_at][null]=false` is every completed sprint and `filter[completed_at][null]=true` every sprint still open.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[completed_at][null]",
                        "in": "query",
                        "description": "When the sprint was actually completed. `filter[completed_at][null]=false` is every completed sprint and `filter[completed_at][null]=true` every sprint still open. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[created_at]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][gte]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][lt]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[end_date]",
                        "in": "query",
                        "description": "Planned last day of the sprint.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[end_date][gte]",
                        "in": "query",
                        "description": "Planned last day of the sprint.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[end_date][lte]",
                        "in": "query",
                        "description": "Planned last day of the sprint.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[end_date][null]",
                        "in": "query",
                        "description": "Planned last day of the sprint. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[is_active]",
                        "in": "query",
                        "description": "Whether the sprint is the project's running one. Combine with `filter[completed_at][null]` to express a sprint state; this filter alone does not distinguish upcoming from completed.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[project_id]",
                        "in": "query",
                        "description": "Narrow the list to one project's sprints, or to several with `in`.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[project_id][in]",
                        "in": "query",
                        "description": "Narrow the list to one project's sprints, or to several with `in`. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[start_date]",
                        "in": "query",
                        "description": "Planned first day of the sprint.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[start_date][gte]",
                        "in": "query",
                        "description": "Planned first day of the sprint.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[start_date][lte]",
                        "in": "query",
                        "description": "Planned first day of the sprint.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[start_date][null]",
                        "in": "query",
                        "description": "Planned first day of the sprint. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[started_at]",
                        "in": "query",
                        "description": "When the sprint was actually started. `filter[started_at][null]=true` is every sprint that has never run.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[started_at][gte]",
                        "in": "query",
                        "description": "When the sprint was actually started. `filter[started_at][null]=true` is every sprint that has never run.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[started_at][lte]",
                        "in": "query",
                        "description": "When the sprint was actually started. `filter[started_at][null]=true` is every sprint that has never run.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[started_at][null]",
                        "in": "query",
                        "description": "When the sprint was actually started. `filter[started_at][null]=true` is every sprint that has never run. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[updated_at]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][gte]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][lt]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/SprintSort"
                    },
                    {
                        "name": "q",
                        "in": "query",
                        "description": "Free-text search over `name`. At most one substring match per request: `q` together with a `contains` filter is 400 invalid-query-parameter.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "minLength": 1
                        }
                    },
                    {
                        "$ref": "#/components/parameters/SprintInclude"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "A page of records.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record.",
                                "schema": {
                                    "type": "string",
                                    "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/SprintList"
                                            }
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/SprintsIndexMeta"
                                        },
                                        "links": {
                                            "$ref": "#/components/schemas/PageLinks"
                                        }
                                    },
                                    "required": [
                                        "data",
                                        "meta",
                                        "links"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "One project's upcoming sprints, in display order",
                        "query": {
                            "filter": {
                                "project_id": 1,
                                "is_active": false,
                                "completed_at": {
                                    "null": true
                                }
                            },
                            "sort": "order"
                        }
                    }
                ],
                "x-plnnrly-permission": "project_management::view_active_sprint",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1,
                    "view_detail_total": 3,
                    "surcharges": {
                        "include": 1,
                        "q": 2
                    }
                },
                "x-plnnrly-token-ability": [
                    "api:read:project_management"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/SprintList",
                    "detail": "#/components/schemas/Sprint"
                }
            },
            "post": {
                "operationId": "api.v2.sprints.store",
                "summary": "Create a sprint",
                "description": "Creates an upcoming sprint on a project and returns it with a `Location` header: `is_active` is `false` and `started_at` and `completed_at` are `null`, whatever `start_date` and `end_date` say; planning dates does not start a sprint. Start it with POST `sprints/{sprint}/actions/start`. `order` is not accepted: the new sprint is placed last within its project and is reordered only in plnnrly. `project_id` is accepted here and only here: a sprint cannot be moved between projects.",
                "tags": [
                    "Sprints"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:project_management"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:project_management"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "project_id": {
                                        "type": "integer",
                                        "x-plnnrly-references": {
                                            "resource": "Project",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "projects",
                                        "description": "The project this sprint belongs to. A sprint cannot be moved between projects, so this is accepted on create only."
                                    },
                                    "name": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "description": "Sprint name as shown in plnnrly, e.g. `Sprint 14`."
                                    },
                                    "description": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "description": "Free-text sprint goal. Plain text, not markup."
                                    },
                                    "start_date": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "format": "date",
                                        "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                                        "description": "Planned first day of the sprint. Independent of `started_at`: planning a date does not start the sprint."
                                    },
                                    "end_date": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "format": "date",
                                        "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                                        "x-plnnrly-constraint": "Must be on or after start_date.",
                                        "description": "Planned last day of the sprint. Independent of `completed_at`."
                                    }
                                },
                                "required": [
                                    "project_id",
                                    "name"
                                ],
                                "additionalProperties": false
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Created.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "Idempotency-Replayed": {
                                "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise.",
                                "schema": {
                                    "type": "boolean",
                                    "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise."
                                }
                            },
                            "Location": {
                                "description": "The absolute URL of the created or queued resource.",
                                "schema": {
                                    "type": "string",
                                    "description": "The absolute URL of the created or queued resource."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Sprint"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "$ref": "#/components/responses/Problem409Idempotency"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "A two-week sprint on a project",
                        "body": {
                            "project_id": 1,
                            "name": "Sprint 14",
                            "description": "Finish the invoicing export.",
                            "start_date": "2026-03-02",
                            "end_date": "2026-03-13"
                        }
                    }
                ],
                "x-plnnrly-permission": "project_management::manage_sprints",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:project_management"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/SprintList",
                    "detail": "#/components/schemas/Sprint"
                }
            },
            "x-plnnrly-allow": [
                "GET",
                "POST"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/sprints/{sprint}": {
            "get": {
                "operationId": "api.v2.sprints.show",
                "summary": "Read a sprint",
                "description": "Returns one sprint in the detail view. The state is derived, not stored: `completed_at` non-`null` means completed, otherwise `is_active` `true` means active, otherwise upcoming. `work_items_count` is the number of items on the sprint and `links.work_items` is the URL that lists them. Reading additionally requires `project_management::manage_sprints`.",
                "tags": [
                    "Sprints"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:project_management"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:project_management"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "sprint",
                        "in": "path",
                        "description": "The Sprint's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/Meta"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Sprint"
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/SprintsShowMeta"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "One sprint with its project expanded",
                        "path": {
                            "sprint": 1
                        },
                        "query": {
                            "include": "project"
                        }
                    }
                ],
                "x-plnnrly-permission": "project_management::view_active_sprint",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:read:project_management"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/SprintList",
                    "detail": "#/components/schemas/Sprint"
                }
            },
            "patch": {
                "operationId": "api.v2.sprints.update",
                "summary": "Update a sprint",
                "description": "Partially updates a sprint's name, description or planned dates: absent keys are untouched, an empty body is rejected. `project_id` is not accepted: a sprint cannot be moved between projects, because its work items and its `order` position are both scoped to the project it was created on. Neither are `order`, `is_active`, `started_at` or `completed_at`: the first is read-only and the other three belong to the start and complete actions. ⚠ `end_date` is checked against `start_date` only when both are sent, so a PATCH carrying an `end_date` earlier than the stored `start_date` succeeds; send the pair together if you need the ordering enforced. Send `If-Match` with the `ETag` from your last read to detect a concurrent edit, which answers `412`. Treat it as best-effort only: this resource carries no concurrency token, and `updated_at` is second-precision, so two writes inside the same second are indistinguishable.",
                "tags": [
                    "Sprints"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:project_management"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:project_management"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "sprint",
                        "in": "path",
                        "description": "The Sprint's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "description": "Sprint name as shown in plnnrly, e.g. `Sprint 14`."
                                    },
                                    "description": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "description": "Free-text sprint goal. Plain text, not markup."
                                    },
                                    "start_date": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "format": "date",
                                        "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                                        "description": "Planned first day of the sprint. Independent of `started_at`: planning a date does not start the sprint."
                                    },
                                    "end_date": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "format": "date",
                                        "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                                        "x-plnnrly-constraint": "Must be on or after start_date.",
                                        "description": "Planned last day of the sprint. Independent of `completed_at`."
                                    }
                                },
                                "additionalProperties": false,
                                "minProperties": 1
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The updated record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Sprint"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Rename a sprint and shift its planned end",
                        "path": {
                            "sprint": 1
                        },
                        "body": {
                            "name": "Sprint 14: invoicing",
                            "end_date": "2026-03-20"
                        },
                        "headers": {
                            "If-Match": "*"
                        }
                    }
                ],
                "x-plnnrly-permission": "project_management::manage_sprints",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:project_management"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/SprintList",
                    "detail": "#/components/schemas/Sprint"
                }
            },
            "delete": {
                "operationId": "api.v2.sprints.destroy",
                "summary": "Delete a sprint",
                "description": "Permanently deletes a sprint. This resource is hard-delete: there is no restore action, no `force` parameter and no `trashed` parameter. ⚠ This endpoint deliberately allows more than plnnrly, which refuses to delete a sprint that is active, completed or still holding work items: over the API all three succeed. The sprint's work items silently return to the project backlog: no work-item event fires, no live update goes out and the affected items' `version` is not bumped. A client holding their ETags will see no change and must re-read them. To close a sprint and keep its history, complete it with POST `sprints/{sprint}/actions/complete` instead.",
                "tags": [
                    "Sprints"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:destroy:project_management"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "sprint",
                        "in": "path",
                        "description": "The Sprint's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "responses": {
                    "204": {
                        "description": "Deleted. No body.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Delete a sprint",
                        "path": {
                            "sprint": 1
                        }
                    }
                ],
                "x-plnnrly-permission": "project_management::manage_sprints",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:destroy:project_management"
                ]
            },
            "x-plnnrly-allow": [
                "GET",
                "PATCH",
                "DELETE"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/sprints/{sprint}/actions/complete": {
            "post": {
                "operationId": "api.v2.sprints.actions.complete",
                "summary": "Complete a sprint",
                "description": "Closes a sprint: clears `is_active` and stamps `completed_at` with the current time, then disposes of the sprint's unresolved work items (those whose workflow status is not a resolution) according to `unresolved_work_items`. Resolved items always stay on the completed sprint, whichever strategy is chosen, so the closed sprint keeps its delivery history. `move_to_next_sprint` additionally requires `next_sprint_id`, which must name a not-yet-completed sprint on the same project other than this one; anything else is `422` `unprocessable-relationship` pointing at `/next_sprint_id`. A sprint that is already completed refuses with `409` `state-conflict` and `conflict.reason` `already_completed`, and one that was never started refuses the same way with `not_started`. Each moved item is saved on its own, so its audit trail records the move and its `version` is bumped (a client holding their ETags must re-read them), but no per-item notification is sent. Returns the sprint in the detail view; `work_items_count` reflects what is left on it.",
                "tags": [
                    "Sprints"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:project_management"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:project_management"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "sprint",
                        "in": "path",
                        "description": "The Sprint's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "unresolved_work_items": {
                                        "type": "string",
                                        "enum": [
                                            "move_to_next_sprint",
                                            "move_to_backlog",
                                            "leave_on_sprint"
                                        ],
                                        "x-enum-descriptions": {
                                            "leave_on_sprint": "Leave the unresolved items on the sprint being completed, so the closed sprint keeps them for its history.",
                                            "move_to_backlog": "Detach the unresolved items from every sprint, so they return to the project backlog.",
                                            "move_to_next_sprint": "Move the unresolved items onto the sprint named by `next_sprint_id`, which is required for this strategy and must be a not-yet-completed sprint on the same project."
                                        },
                                        "x-extensible-enum": true,
                                        "description": "What happens to the sprint's unresolved work items."
                                    },
                                    "next_sprint_id": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "x-plnnrly-constraint": "Required when unresolved_work_items, move_to_next_sprint.",
                                        "x-plnnrly-references": {
                                            "resource": "Sprint",
                                            "field": "id"
                                        },
                                        "description": "Sprint that receives the unresolved work items. Required, and only used, with `move_to_next_sprint`; it must be a not-yet-completed sprint on the same project."
                                    }
                                },
                                "required": [
                                    "unresolved_work_items"
                                ],
                                "additionalProperties": false
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The record after the action ran.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "Idempotency-Replayed": {
                                "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise.",
                                "schema": {
                                    "type": "boolean",
                                    "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Sprint"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "description": "Conflict.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "oneOf": [
                                        {
                                            "$ref": "#/components/schemas/IdempotencyKeyInFlightProblem"
                                        },
                                        {
                                            "$ref": "#/components/schemas/IdempotencyKeyReusedProblem"
                                        },
                                        {
                                            "$ref": "#/components/schemas/StateConflictProblem"
                                        }
                                    ],
                                    "discriminator": {
                                        "propertyName": "type",
                                        "mapping": {
                                            "https://developers.plnnrly.com/api/v2/problems/idempotency-key-in-flight": "#/components/schemas/IdempotencyKeyInFlightProblem",
                                            "https://developers.plnnrly.com/api/v2/problems/idempotency-key-reused": "#/components/schemas/IdempotencyKeyReusedProblem",
                                            "https://developers.plnnrly.com/api/v2/problems/state-conflict": "#/components/schemas/StateConflictProblem"
                                        }
                                    }
                                },
                                "examples": {
                                    "idempotency-key-in-flight": {
                                        "$ref": "#/components/examples/IdempotencyKeyInFlightProblem"
                                    },
                                    "idempotency-key-reused": {
                                        "$ref": "#/components/examples/IdempotencyKeyReusedProblem"
                                    },
                                    "state-conflict": {
                                        "$ref": "#/components/examples/StateConflictProblem"
                                    }
                                }
                            }
                        }
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Close the sprint and roll unfinished work into the next one",
                        "path": {
                            "sprint": 1
                        },
                        "body": {
                            "unresolved_work_items": "move_to_next_sprint",
                            "next_sprint_id": 1
                        }
                    }
                ],
                "x-plnnrly-permission": "project_management::manage_sprints",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:project_management"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/SprintList",
                    "detail": "#/components/schemas/Sprint"
                }
            },
            "x-plnnrly-allow": [
                "POST"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/sprints/{sprint}/actions/start": {
            "post": {
                "operationId": "api.v2.sprints.actions.start",
                "summary": "Start a sprint",
                "description": "Activates a sprint: sets `is_active` to `true` and stamps `started_at` with the current time. The planned `start_date` and `end_date` are left alone (planning dates and running a sprint are independent), and `order` is not touched. At most one sprint per project may run at a time, so three states refuse with `409` `state-conflict`: a completed sprint (`conflict.reason` `already_completed`), a sprint that is already running (`already_started`), and a sprint whose project already has a different running sprint (`already_started`, with the blocking sprint named in the conflict member; complete it first). Returns the sprint in the detail view with a fresh `ETag`. Takes no request body.",
                "tags": [
                    "Sprints"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:project_management"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:project_management"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "sprint",
                        "in": "path",
                        "description": "The Sprint's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The record after the action ran.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "Idempotency-Replayed": {
                                "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise.",
                                "schema": {
                                    "type": "boolean",
                                    "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Sprint"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "description": "Conflict.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "oneOf": [
                                        {
                                            "$ref": "#/components/schemas/IdempotencyKeyInFlightProblem"
                                        },
                                        {
                                            "$ref": "#/components/schemas/IdempotencyKeyReusedProblem"
                                        },
                                        {
                                            "$ref": "#/components/schemas/StateConflictProblem"
                                        }
                                    ],
                                    "discriminator": {
                                        "propertyName": "type",
                                        "mapping": {
                                            "https://developers.plnnrly.com/api/v2/problems/idempotency-key-in-flight": "#/components/schemas/IdempotencyKeyInFlightProblem",
                                            "https://developers.plnnrly.com/api/v2/problems/idempotency-key-reused": "#/components/schemas/IdempotencyKeyReusedProblem",
                                            "https://developers.plnnrly.com/api/v2/problems/state-conflict": "#/components/schemas/StateConflictProblem"
                                        }
                                    }
                                },
                                "examples": {
                                    "idempotency-key-in-flight": {
                                        "$ref": "#/components/examples/IdempotencyKeyInFlightProblem"
                                    },
                                    "idempotency-key-reused": {
                                        "$ref": "#/components/examples/IdempotencyKeyReusedProblem"
                                    },
                                    "state-conflict": {
                                        "$ref": "#/components/examples/StateConflictProblem"
                                    }
                                }
                            }
                        }
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Start the next sprint",
                        "path": {
                            "sprint": 1
                        },
                        "body": {}
                    }
                ],
                "x-plnnrly-permission": "project_management::manage_sprints",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:project_management"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/SprintList",
                    "detail": "#/components/schemas/Sprint"
                }
            },
            "x-plnnrly-allow": [
                "POST"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/subscription-items/{subscription_item}": {
            "get": {
                "operationId": "api.v2.subscription-items.show",
                "summary": "Read a subscription line",
                "description": "Returns one subscription line in the detail view. The description is published exactly as stored, with any {{$domain}}-style placeholders unresolved: those are filled in only when a renewal turns the line into an invoice row. Lines are soft-deleted, so a deleted line is `404` unless `trashed=with` or `trashed=only` is passed; `trashed=only` 404s a live line. This flat path performs no lookup of the parent subscription: a line is readable by id to any caller holding `customer_subscriptions::read`, which is what plnnrly does too.",
                "tags": [
                    "Billing"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:customer_subscriptions"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:customer_subscriptions"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "subscription_item",
                        "in": "path",
                        "description": "The SubscriptionItem's numeric id. Accepts a soft-deleted record.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/Trashed"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/SubscriptionItem"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "One line with its product expanded",
                        "path": {
                            "subscription_item": 1
                        },
                        "query": {
                            "include": "subscribable"
                        }
                    }
                ],
                "x-plnnrly-feature": "billing",
                "x-plnnrly-permission": "customer_subscriptions::read",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:read:customer_subscriptions"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/SubscriptionItemList",
                    "detail": "#/components/schemas/SubscriptionItem"
                }
            },
            "patch": {
                "operationId": "api.v2.subscription-items.update",
                "summary": "Update a subscription line",
                "description": "Partially updates one line: absent keys are untouched. Send `If-Match` with the `ETag` from your last read to detect a concurrent edit, which answers `412`. `vat_amount` and `total_amount` are recomputed by the server from quantity, `sales_price` and `vat_rate` and are never accepted. `customer_subscription_id` and `sort_order` are not accepted either: a line cannot be moved to another subscription, and there is no way to reorder lines through the API. Repointing subscribable away from a hosting product also clears the hosting site or server linked to the line in plnnrly; that link is not published by this API, so send subscribable only when you mean to change what is being sold. A deleted line answers `404` unless `trashed=with` is passed.",
                "tags": [
                    "Billing"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:customer_subscriptions"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:customer_subscriptions"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "subscription_item",
                        "in": "path",
                        "description": "The SubscriptionItem's numeric id. Accepts a soft-deleted record.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/Trashed"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "description": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "description": "The line text, as printed on the invoice a renewal generates. May contain placeholders such as {{$domain}}; the API publishes the raw stored text and those are resolved only at invoice time."
                                    },
                                    "quantity": {
                                        "type": "integer",
                                        "minimum": 1,
                                        "description": "Number of units billed per period."
                                    },
                                    "purchase_price": {
                                        "type": "string",
                                        "format": "decimal",
                                        "pattern": "^\\d+(\\.\\d{1,2})?$",
                                        "x-plnnrly-constraint": "At least 0. At most 99999999.99.",
                                        "x-plnnrly-maximum": 99999999.99,
                                        "x-plnnrly-minimum": 0,
                                        "description": "Cost price per unit, for margin bookkeeping only. It feeds neither generated amount below and appears on no invoice."
                                    },
                                    "sales_price": {
                                        "type": "string",
                                        "format": "decimal",
                                        "pattern": "^\\d+(\\.\\d{1,2})?$",
                                        "x-plnnrly-constraint": "At least 0. At most 99999999.99.",
                                        "x-plnnrly-maximum": 99999999.99,
                                        "x-plnnrly-minimum": 0,
                                        "description": "Sales price per unit, excluding VAT. This is what actually bills: a renewal copies it into the generated invoice row's unit price."
                                    },
                                    "vat_rate": {
                                        "type": "integer",
                                        "minimum": 0,
                                        "maximum": 100,
                                        "description": "VAT percentage as a whole number: 21 means 21%."
                                    },
                                    "subscribable_type": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "enum": [
                                            "subscription_product",
                                            null
                                        ],
                                        "x-plnnrly-constraint": "Required when subscribable_id is present.",
                                        "description": "What this line sells. Rendered as {\"id\": N} until named in `?include=subscribable`, which additionally requires the read permission and token scope of the `subscription_products` prefix. Lines that bill a hosting domain or a server carry a `subscribable_type` this API publishes no shape for, and keep the {\"id\": N} form however they are requested; identify those from `subscribable_type` and `subscribable_id` yourself. Null on both halves is a free-text line priced by hand."
                                    },
                                    "subscribable_id": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "x-plnnrly-constraint": "Required when subscribable_type is present.",
                                        "x-plnnrly-references": {
                                            "resource": "SubscriptionProduct",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "subscription_products",
                                        "description": "What this line sells. Rendered as {\"id\": N} until named in `?include=subscribable`, which additionally requires the read permission and token scope of the `subscription_products` prefix. Lines that bill a hosting domain or a server carry a `subscribable_type` this API publishes no shape for, and keep the {\"id\": N} form however they are requested; identify those from `subscribable_type` and `subscribable_id` yourself. Null on both halves is a free-text line priced by hand."
                                    },
                                    "exact_item_id": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 255,
                                        "description": "Exact Online article identifier this line bills against. Optional: a renewal falls back to the subscription product's own Exact values and then to the organisation's Exact defaults, so leaving it empty does not break the sync."
                                    },
                                    "exact_vat_code": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 255,
                                        "description": "Exact Online VAT code, by code rather than by id."
                                    },
                                    "exact_gl_account": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 255,
                                        "description": "Exact Online general-ledger revenue account for this line."
                                    },
                                    "exact_cost_center": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 255,
                                        "description": "Exact Online cost-centre code."
                                    }
                                },
                                "additionalProperties": false,
                                "minProperties": 1
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The updated record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/SubscriptionItem"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Reprice a line",
                        "path": {
                            "subscription_item": 1
                        },
                        "body": {
                            "sales_price": "14.95"
                        },
                        "headers": {
                            "If-Match": "*"
                        }
                    }
                ],
                "x-plnnrly-feature": "billing",
                "x-plnnrly-permission": "customer_subscriptions::update",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:customer_subscriptions"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/SubscriptionItemList",
                    "detail": "#/components/schemas/SubscriptionItem"
                }
            },
            "delete": {
                "operationId": "api.v2.subscription-items.destroy",
                "summary": "Delete a subscription line",
                "description": "Soft-deletes one line. There is no permanent delete and no restore verb: a line comes back by being re-created through `POST customer-subscriptions/{customer_subscription}/items`, or by restoring the subscription it belonged to. Because this is a soft delete, invoice rows a past renewal generated from the line keep pointing at it. Removing the last line of a subscription is allowed, but that subscription can no longer be invoiced: its next renewal run logs a failure instead of producing an invoice. Re-deleting an already-deleted line answers `204`, so a retry is safe.",
                "tags": [
                    "Billing"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:destroy:customer_subscriptions"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "subscription_item",
                        "in": "path",
                        "description": "The SubscriptionItem's numeric id. Accepts a soft-deleted record.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "responses": {
                    "204": {
                        "description": "Deleted. No body.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Remove a line",
                        "path": {
                            "subscription_item": 1
                        }
                    }
                ],
                "x-plnnrly-feature": "billing",
                "x-plnnrly-permission": "customer_subscriptions::delete",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:destroy:customer_subscriptions"
                ]
            },
            "x-plnnrly-allow": [
                "GET",
                "PATCH",
                "DELETE"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/subscription-products": {
            "get": {
                "operationId": "api.v2.subscription-products.index",
                "summary": "List subscription products",
                "description": "Returns the organisation's sellable subscription products, sorted by name A to Z. Defaults to the summary view; `view=detail` carries the same fields on this resource (no field of it is detail-only) and costs three read units instead of one. Search with `q=` over the product name, and pass `trashed=with|only` to see deleted products.",
                "tags": [
                    "Billing"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:subscription_products"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:subscription_products"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/PerPage"
                    },
                    {
                        "$ref": "#/components/parameters/Cursor"
                    },
                    {
                        "$ref": "#/components/parameters/PageNumber"
                    },
                    {
                        "$ref": "#/components/parameters/ViewSummary"
                    },
                    {
                        "$ref": "#/components/parameters/Trashed"
                    },
                    {
                        "name": "filter[created_at]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][gte]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][lt]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[is_hosting]",
                        "in": "query",
                        "description": "Whether the product is sold against a concrete site or server.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[price_interval]",
                        "in": "query",
                        "description": "Billing interval the product is priced per.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "yearly",
                                "monthly",
                                "one-time"
                            ],
                            "x-extensible-enum": true
                        }
                    },
                    {
                        "name": "filter[price_interval][ne]",
                        "in": "query",
                        "description": "Billing interval the product is priced per.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "yearly",
                                "monthly",
                                "one-time"
                            ],
                            "x-extensible-enum": true
                        }
                    },
                    {
                        "name": "filter[price_interval][in]",
                        "in": "query",
                        "description": "Billing interval the product is priced per. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "string",
                                "enum": [
                                    "yearly",
                                    "monthly",
                                    "one-time"
                                ],
                                "x-extensible-enum": true
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[updated_at]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][gte]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][lt]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/SubscriptionProductSort"
                    },
                    {
                        "name": "q",
                        "in": "query",
                        "description": "Free-text search over `name`. At most one substring match per request: `q` together with a `contains` filter is 400 invalid-query-parameter.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "minLength": 1
                        }
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "A page of records.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record.",
                                "schema": {
                                    "type": "string",
                                    "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/SubscriptionProductList"
                                            }
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/SubscriptionProductsIndexMeta"
                                        },
                                        "links": {
                                            "$ref": "#/components/schemas/PageLinks"
                                        }
                                    },
                                    "required": [
                                        "data",
                                        "meta",
                                        "links"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Hosting products by name",
                        "query": {
                            "filter[is_hosting]": "true",
                            "sort": "name"
                        }
                    }
                ],
                "x-plnnrly-feature": "billing",
                "x-plnnrly-permission": "subscription_products::read",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1,
                    "view_detail_total": 3,
                    "surcharges": {
                        "q": 2
                    }
                },
                "x-plnnrly-token-ability": [
                    "api:read:subscription_products"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/SubscriptionProductList",
                    "detail": "#/components/schemas/SubscriptionProduct"
                }
            },
            "post": {
                "operationId": "api.v2.subscription-products.store",
                "summary": "Create a subscription product",
                "description": "Creates a product and returns it in the detail view with a `Location` header. Names are unique and deleted products count, so a name that matches a soft-deleted product is a `422` `validation-failed` with code unique; restore that product with POST `subscription-products/{subscription_product}/actions/restore` rather than retrying under a different name. `price_interval` is required: it has no default, and the monthly pre-selection in plnnrly's form is a convenience only.",
                "tags": [
                    "Billing"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:subscription_products"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:subscription_products"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "x-plnnrly-constraint": "Must be unique across SubscriptionProduct.",
                                        "x-plnnrly-unique": {
                                            "resource": "SubscriptionProduct",
                                            "field": "name"
                                        },
                                        "description": "Product name as shown in the UI and on the subscription line."
                                    },
                                    "description": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "description": "Invoice-line template for subscription lines built from this product. Supports the placeholder tokens {{$domain}}, {{$site}}, {{$server}}, {{$period}}, {{$product}} and {{$customer}}, which are filled in when the line is invoiced; the template itself is kept verbatim."
                                    },
                                    "price_interval": {
                                        "type": "string",
                                        "enum": [
                                            "yearly",
                                            "monthly",
                                            "one-time"
                                        ],
                                        "x-enum-descriptions": {
                                            "monthly": "Billed every month.",
                                            "one-time": "Charged once, on the invoice the product is first put on, and never renewed.",
                                            "yearly": "Billed once a year."
                                        },
                                        "x-extensible-enum": true,
                                        "description": "Interval the sales price is charged per."
                                    },
                                    "is_hosting": {
                                        "type": "boolean",
                                        "description": "Whether this is a hosting product: one sold against a concrete site or server, which is what lets a subscription line name the infrastructure it covers."
                                    },
                                    "purchase_price": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "format": "decimal",
                                        "pattern": "^\\d+(\\.\\d{1,2})?$",
                                        "x-plnnrly-constraint": "At least 0.",
                                        "x-plnnrly-minimum": 0,
                                        "description": "What the product costs us, as a decimal string. Null when unknown."
                                    },
                                    "sales_price": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "format": "decimal",
                                        "pattern": "^\\d+(\\.\\d{1,2})?$",
                                        "x-plnnrly-constraint": "At least 0.",
                                        "x-plnnrly-minimum": 0,
                                        "description": "What the customer is billed per interval, as a decimal string. Null when the line carries its own price."
                                    },
                                    "vat_rate": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "minimum": 0,
                                        "maximum": 100,
                                        "description": "VAT rate as a whole percentage: 21 means 21%, not 0.21. Null when the rate is decided at invoicing time."
                                    },
                                    "exact_item_id": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 255,
                                        "description": "Exact Online article this product maps to, by Exact's own external identifier (`exact_items.exact_id`), not by a local id."
                                    },
                                    "exact_vat_code": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 255,
                                        "description": "Exact Online VAT code, by code (`exact_vat_codes.code`), not by id."
                                    },
                                    "exact_cost_center": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 255,
                                        "description": "Exact Online cost centre, by code (`exact_cost_centers.code`), not by id."
                                    }
                                },
                                "required": [
                                    "name",
                                    "price_interval"
                                ],
                                "additionalProperties": false
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Created.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "Idempotency-Replayed": {
                                "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise.",
                                "schema": {
                                    "type": "boolean",
                                    "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise."
                                }
                            },
                            "Location": {
                                "description": "The absolute URL of the created or queued resource.",
                                "schema": {
                                    "type": "string",
                                    "description": "The absolute URL of the created or queued resource."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/SubscriptionProduct"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "$ref": "#/components/responses/Problem409Idempotency"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "A monthly hosting product",
                        "body": {
                            "name": "Managed hosting, small",
                            "description": "Managed hosting for {{$site}} over {{$period}}.",
                            "price_interval": "monthly",
                            "is_hosting": true,
                            "sales_price": "49.50",
                            "vat_rate": 21
                        }
                    }
                ],
                "x-plnnrly-feature": "billing",
                "x-plnnrly-permission": "subscription_products::create",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:subscription_products"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/SubscriptionProductList",
                    "detail": "#/components/schemas/SubscriptionProduct"
                }
            },
            "x-plnnrly-allow": [
                "GET",
                "POST"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/subscription-products/{subscription_product}": {
            "get": {
                "operationId": "api.v2.subscription-products.show",
                "summary": "Read a subscription product",
                "description": "Returns one product in the detail view. Products are soft-deleted, so a deleted product is `404` unless `trashed=with` or `trashed=only` is passed; `trashed=only` 404s a live product.",
                "tags": [
                    "Billing"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:subscription_products"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:subscription_products"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "subscription_product",
                        "in": "path",
                        "description": "The SubscriptionProduct's numeric id. Accepts a soft-deleted record.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/Trashed"
                    },
                    {
                        "$ref": "#/components/parameters/Meta"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/SubscriptionProduct"
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/SubscriptionProductsShowMeta"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "One product",
                        "path": {
                            "subscription_product": 1
                        }
                    }
                ],
                "x-plnnrly-feature": "billing",
                "x-plnnrly-permission": "subscription_products::read",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:read:subscription_products"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/SubscriptionProductList",
                    "detail": "#/components/schemas/SubscriptionProduct"
                }
            },
            "patch": {
                "operationId": "api.v2.subscription-products.update",
                "summary": "Update a subscription product",
                "description": "Partially updates a product: absent keys are untouched. Send `If-Match` with the `ETag` from your last read to detect a concurrent edit, which answers `412`. Renaming a product onto another product's name, a deleted one included, is a `422` `validation-failed` with code unique. Existing subscription lines keep the price they were created with; a price change here applies to lines created afterwards.",
                "tags": [
                    "Billing"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:subscription_products"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:subscription_products"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "subscription_product",
                        "in": "path",
                        "description": "The SubscriptionProduct's numeric id. Accepts a soft-deleted record.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/Trashed"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "x-plnnrly-constraint": "Must be unique across SubscriptionProduct.",
                                        "x-plnnrly-unique": {
                                            "resource": "SubscriptionProduct",
                                            "field": "name"
                                        },
                                        "description": "Product name as shown in the UI and on the subscription line."
                                    },
                                    "description": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "description": "Invoice-line template for subscription lines built from this product. Supports the placeholder tokens {{$domain}}, {{$site}}, {{$server}}, {{$period}}, {{$product}} and {{$customer}}, which are filled in when the line is invoiced; the template itself is kept verbatim."
                                    },
                                    "price_interval": {
                                        "type": "string",
                                        "enum": [
                                            "yearly",
                                            "monthly",
                                            "one-time"
                                        ],
                                        "x-enum-descriptions": {
                                            "monthly": "Billed every month.",
                                            "one-time": "Charged once, on the invoice the product is first put on, and never renewed.",
                                            "yearly": "Billed once a year."
                                        },
                                        "x-extensible-enum": true,
                                        "description": "Interval the sales price is charged per."
                                    },
                                    "is_hosting": {
                                        "type": "boolean",
                                        "description": "Whether this is a hosting product: one sold against a concrete site or server, which is what lets a subscription line name the infrastructure it covers."
                                    },
                                    "purchase_price": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "format": "decimal",
                                        "pattern": "^\\d+(\\.\\d{1,2})?$",
                                        "x-plnnrly-constraint": "At least 0.",
                                        "x-plnnrly-minimum": 0,
                                        "description": "What the product costs us, as a decimal string. Null when unknown."
                                    },
                                    "sales_price": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "format": "decimal",
                                        "pattern": "^\\d+(\\.\\d{1,2})?$",
                                        "x-plnnrly-constraint": "At least 0.",
                                        "x-plnnrly-minimum": 0,
                                        "description": "What the customer is billed per interval, as a decimal string. Null when the line carries its own price."
                                    },
                                    "vat_rate": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "minimum": 0,
                                        "maximum": 100,
                                        "description": "VAT rate as a whole percentage: 21 means 21%, not 0.21. Null when the rate is decided at invoicing time."
                                    },
                                    "exact_item_id": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 255,
                                        "description": "Exact Online article this product maps to, by Exact's own external identifier (`exact_items.exact_id`), not by a local id."
                                    },
                                    "exact_vat_code": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 255,
                                        "description": "Exact Online VAT code, by code (`exact_vat_codes.code`), not by id."
                                    },
                                    "exact_cost_center": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 255,
                                        "description": "Exact Online cost centre, by code (`exact_cost_centers.code`), not by id."
                                    }
                                },
                                "additionalProperties": false,
                                "minProperties": 1
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The updated record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/SubscriptionProduct"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Raise the sales price",
                        "path": {
                            "subscription_product": 1
                        },
                        "body": {
                            "sales_price": "54.95"
                        },
                        "headers": {
                            "If-Match": "*"
                        }
                    }
                ],
                "x-plnnrly-feature": "billing",
                "x-plnnrly-permission": "subscription_products::update",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:subscription_products"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/SubscriptionProductList",
                    "detail": "#/components/schemas/SubscriptionProduct"
                }
            },
            "delete": {
                "operationId": "api.v2.subscription-products.destroy",
                "summary": "Delete a subscription product",
                "description": "Soft-deletes a product: it is only marked deleted, and POST `subscription-products/{subscription_product}/actions/restore` reverses it. There is no permanent delete. Subscription lines and subscription-type lines that point at this product through `subscribable` are not deleted and not cleared, so those lines keep referencing a product that has become invisible to the catalogue, and a deleted hosting product stops reading as a hosting product in plnnrly until it is restored. Deleting a product does not release its name: deleted products count towards uniqueness.",
                "tags": [
                    "Billing"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:destroy:subscription_products"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "subscription_product",
                        "in": "path",
                        "description": "The SubscriptionProduct's numeric id. Accepts a soft-deleted record.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "responses": {
                    "204": {
                        "description": "Deleted. No body.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Delete a product",
                        "path": {
                            "subscription_product": 1
                        }
                    }
                ],
                "x-plnnrly-feature": "billing",
                "x-plnnrly-permission": "subscription_products::delete",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:destroy:subscription_products"
                ]
            },
            "x-plnnrly-allow": [
                "GET",
                "PATCH",
                "DELETE"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/subscription-products/{subscription_product}/actions/restore": {
            "post": {
                "operationId": "api.v2.subscription-products.actions.restore",
                "summary": "Restore a deleted subscription product",
                "description": "Clears the soft-delete marker and returns the product in the detail view. Restoring additionally requires `subscription_products::delete` as well as `subscription_products::create`. Restoring a product that is not deleted succeeds and returns an `already_in_target_state` warning. The restore finds a deleted product on its own, so the trashed parameter is not accepted. This is also the remedy for a `422` unique on the name: the uniqueness check counts deleted products, so restoring the product that holds the name is the fix, not retrying under another one.",
                "tags": [
                    "Billing"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:subscription_products"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:subscription_products"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "subscription_product",
                        "in": "path",
                        "description": "The SubscriptionProduct's numeric id. Accepts a soft-deleted record.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The record after the action ran.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "Idempotency-Replayed": {
                                "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise.",
                                "schema": {
                                    "type": "boolean",
                                    "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/SubscriptionProduct"
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/SubscriptionProductsActionsRestoreMeta"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "$ref": "#/components/responses/Problem409Idempotency"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Restore a deleted product",
                        "path": {
                            "subscription_product": 1
                        },
                        "body": {}
                    }
                ],
                "x-plnnrly-feature": "billing",
                "x-plnnrly-permission": "subscription_products::create",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:subscription_products"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/SubscriptionProductList",
                    "detail": "#/components/schemas/SubscriptionProduct"
                }
            },
            "x-plnnrly-allow": [
                "POST"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/subscription-type-items/{subscription_type_item}": {
            "get": {
                "operationId": "api.v2.subscription-type-items.show",
                "summary": "Read a template line",
                "description": "Returns one template line in the detail view. Template lines are soft-deleted, so a deleted line is `404` unless `trashed=with` or `trashed=only` is passed; `trashed=only` 404s a live line. This flat path performs no lookup of the parent subscription type: a line is readable by id to any caller holding `subscription_types::read`, which is what plnnrly does too. `subscribable_type` can read back as domain or server on a line pointed at a hosting record in plnnrly, in which case subscribable is the bare {\"id\": N} reference rather than an expandable object.",
                "tags": [
                    "Billing"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:subscription_types"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:subscription_types"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "subscription_type_item",
                        "in": "path",
                        "description": "The SubscriptionTypeItem's numeric id. Accepts a soft-deleted record.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/Trashed"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/SubscriptionTypeItem"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "One line with its type and its product expanded",
                        "path": {
                            "subscription_type_item": 1
                        },
                        "query": {
                            "include": "subscription_type,subscribable"
                        }
                    }
                ],
                "x-plnnrly-feature": "billing",
                "x-plnnrly-permission": "subscription_types::read",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:read:subscription_types"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/SubscriptionTypeItemList",
                    "detail": "#/components/schemas/SubscriptionTypeItem"
                }
            },
            "patch": {
                "operationId": "api.v2.subscription-type-items.update",
                "summary": "Update a template line",
                "description": "Partially updates one template line: absent keys are untouched, and an empty body is `422`. Send `If-Match` with the `ETag` from your last read to detect a concurrent edit, which answers `412`. `subscription_type_id` and `sort_order` are not accepted: a line cannot be moved to another type, and there is no way to reorder template lines through the API. `subscribable_type` and `subscribable_id` stay all-or-nothing: send both to re-point the line at another subscription product, or both as `null` to clear it. Only `subscription_product` is accepted, so a line pointed at a domain or a server in plnnrly can be read but never re-pointed here. Nothing is recalculated on the subscription type, and the change does not reach customer subscriptions already created from this template: that copy is taken by value at pick time.",
                "tags": [
                    "Billing"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:subscription_types"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:subscription_types"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "subscription_type_item",
                        "in": "path",
                        "description": "The SubscriptionTypeItem's numeric id. Accepts a soft-deleted record.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/Trashed"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 255,
                                        "description": "Optional label for the template line. plnnrly's form does not show it, so most existing lines carry `null`; it exists for programmatic naming and is what `?q=` searches."
                                    },
                                    "description": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "description": "Invoice-line text template, copied verbatim onto the customer subscription's line and resolved only when that line is invoiced. Placeholder tokens survive the copy unresolved: {{$domain}}, {{$period}}, {{$product}}, {{$customer}}, {{$site}}, {{$server}}, plus the Dutch aliases {{$domein}}, {{$periode}} and {{$klant}}."
                                    },
                                    "quantity": {
                                        "type": "integer",
                                        "minimum": 1,
                                        "description": "Number of units the template line carries. Whole units: this is an integer, not the fractional quantity an invoice row uses."
                                    },
                                    "default_purchase_price": {
                                        "type": "string",
                                        "format": "decimal",
                                        "pattern": "^-?\\d+(\\.\\d{1,2})?$",
                                        "x-plnnrly-constraint": "At least -99999999.99. At most 99999999.99.",
                                        "x-plnnrly-maximum": 99999999.99,
                                        "x-plnnrly-minimum": -99999999.99,
                                        "description": "Purchase price per unit, excluding VAT, copied onto a new customer subscription's line."
                                    },
                                    "default_sales_price": {
                                        "type": "string",
                                        "format": "decimal",
                                        "pattern": "^-?\\d+(\\.\\d{1,2})?$",
                                        "x-plnnrly-constraint": "At least -99999999.99. At most 99999999.99.",
                                        "x-plnnrly-maximum": 99999999.99,
                                        "x-plnnrly-minimum": -99999999.99,
                                        "description": "Sales price per unit, excluding VAT, copied onto a new customer subscription's line. This is what eventually bills."
                                    },
                                    "vat_rate": {
                                        "type": "integer",
                                        "minimum": 0,
                                        "maximum": 100,
                                        "description": "VAT percentage as a whole number: 21 means 21%."
                                    },
                                    "subscribable_type": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "enum": [
                                            "subscription_product",
                                            null
                                        ],
                                        "x-plnnrly-constraint": "Required when subscribable_id is present.",
                                        "description": "The record this template line sells, normally a subscription product. Null on a line that sells nothing in particular, and a bare {\"id\": N} reference with `subscribable_type` of `domain` or `server` on a line pointed at a hosting record, which only plnnrly itself can do. Expanding it with `?include=subscribable` additionally requires `subscription_products` read permission and a token scope covering that prefix."
                                    },
                                    "subscribable_id": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "x-plnnrly-constraint": "Required when subscribable_type is present.",
                                        "x-plnnrly-references": {
                                            "resource": "SubscriptionProduct",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "subscription_products",
                                        "description": "The record this template line sells, normally a subscription product. Null on a line that sells nothing in particular, and a bare {\"id\": N} reference with `subscribable_type` of `domain` or `server` on a line pointed at a hosting record, which only plnnrly itself can do. Expanding it with `?include=subscribable` additionally requires `subscription_products` read permission and a token scope covering that prefix."
                                    }
                                },
                                "additionalProperties": false,
                                "minProperties": 1
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The updated record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/SubscriptionTypeItem"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Raise the template's sales price",
                        "path": {
                            "subscription_type_item": 1
                        },
                        "body": {
                            "default_sales_price": "34.95"
                        },
                        "headers": {
                            "If-Match": "*"
                        }
                    }
                ],
                "x-plnnrly-feature": "billing",
                "x-plnnrly-permission": "subscription_types::update",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:subscription_types"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/SubscriptionTypeItemList",
                    "detail": "#/components/schemas/SubscriptionTypeItem"
                }
            },
            "delete": {
                "operationId": "api.v2.subscription-type-items.destroy",
                "summary": "Delete a template line",
                "description": "Soft-deletes one template line. There is no permanent delete and no restore verb: a line comes back by being re-created through POST `subscription-types/{subscription_type}/items`, or by restoring the type it belonged to. Customer subscriptions already built from this template keep their own lines untouched (those were copied by value when the type was picked, not referenced), so deleting a template line changes nothing that is currently billing. Re-deleting an already-deleted line answers `204`, so a retry is safe.",
                "tags": [
                    "Billing"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:destroy:subscription_types"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "subscription_type_item",
                        "in": "path",
                        "description": "The SubscriptionTypeItem's numeric id. Accepts a soft-deleted record.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "responses": {
                    "204": {
                        "description": "Deleted. No body.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Remove a template line",
                        "path": {
                            "subscription_type_item": 1
                        }
                    }
                ],
                "x-plnnrly-feature": "billing",
                "x-plnnrly-permission": "subscription_types::delete",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:destroy:subscription_types"
                ]
            },
            "x-plnnrly-allow": [
                "GET",
                "PATCH",
                "DELETE"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/subscription-types": {
            "get": {
                "operationId": "api.v2.subscription-types.index",
                "summary": "List subscription types",
                "description": "Returns the organisation's subscription types in catalogue order: `sort_order` ascending, the same order plnnrly shows. Defaults to the summary view; `view=detail` carries the same fields on this resource (no field of it is detail-only) and costs three read units instead of one. Search with `q=` over the type name, and pass `trashed=with|only` to see deleted types.",
                "tags": [
                    "Billing"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:subscription_types"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:subscription_types"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/PerPage"
                    },
                    {
                        "$ref": "#/components/parameters/Cursor"
                    },
                    {
                        "$ref": "#/components/parameters/SubscriptionTypePageNumber"
                    },
                    {
                        "$ref": "#/components/parameters/ViewSummary"
                    },
                    {
                        "$ref": "#/components/parameters/Trashed"
                    },
                    {
                        "name": "filter[active]",
                        "in": "query",
                        "description": "Whether the type is offered in the catalogue.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[auto_renew]",
                        "in": "query",
                        "description": "Whether subscriptions of this type renew automatically by default.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[created_at]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][gte]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][lt]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[default_billing_interval]",
                        "in": "query",
                        "description": "Interval new subscriptions of this type are billed at by default.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "yearly",
                                "monthly",
                                "one-time"
                            ],
                            "x-extensible-enum": true
                        }
                    },
                    {
                        "name": "filter[default_billing_interval][ne]",
                        "in": "query",
                        "description": "Interval new subscriptions of this type are billed at by default.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "yearly",
                                "monthly",
                                "one-time"
                            ],
                            "x-extensible-enum": true
                        }
                    },
                    {
                        "name": "filter[default_billing_interval][in]",
                        "in": "query",
                        "description": "Interval new subscriptions of this type are billed at by default. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "string",
                                "enum": [
                                    "yearly",
                                    "monthly",
                                    "one-time"
                                ],
                                "x-extensible-enum": true
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[updated_at]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][gte]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][lt]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/SubscriptionTypeSort"
                    },
                    {
                        "name": "q",
                        "in": "query",
                        "description": "Free-text search over `name`. At most one substring match per request: `q` together with a `contains` filter is 400 invalid-query-parameter.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "minLength": 1
                        }
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "A page of records.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record.",
                                "schema": {
                                    "type": "string",
                                    "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/SubscriptionTypeList"
                                            }
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/SubscriptionTypesIndexMeta"
                                        },
                                        "links": {
                                            "$ref": "#/components/schemas/PageLinks"
                                        }
                                    },
                                    "required": [
                                        "data",
                                        "meta",
                                        "links"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Active types in catalogue order",
                        "query": {
                            "filter[active]": "true",
                            "sort": "sort_order"
                        }
                    }
                ],
                "x-plnnrly-feature": "billing",
                "x-plnnrly-permission": "subscription_types::read",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1,
                    "view_detail_total": 3,
                    "surcharges": {
                        "q": 2
                    }
                },
                "x-plnnrly-token-ability": [
                    "api:read:subscription_types"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/SubscriptionTypeList",
                    "detail": "#/components/schemas/SubscriptionType"
                }
            },
            "post": {
                "operationId": "api.v2.subscription-types.store",
                "summary": "Create a subscription type",
                "description": "Creates a type and returns it in the detail view with a `Location` header. `sort_order` is assigned by the server as one past the highest existing position and cannot be sent. Names are not unique, so two types may share one. Template lines are not accepted here: create them one request each through POST `subscription-types/{subscription_type}/items`, so sending items is a `422` `validation-failed` for an unknown key.",
                "tags": [
                    "Billing"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:subscription_types"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:subscription_types"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "description": "Subscription type name. It is also the name every customer subscription of this type is displayed under, so renaming a type renames what its existing subscriptions read as. Names are not unique."
                                    },
                                    "description": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "description": "What subscriptions of this type cover, as free text."
                                    },
                                    "default_billing_interval": {
                                        "type": "string",
                                        "enum": [
                                            "yearly",
                                            "monthly",
                                            "one-time"
                                        ],
                                        "x-enum-descriptions": {
                                            "monthly": "Billed every month.",
                                            "one-time": "Charged once, on the invoice the product is first put on, and never renewed.",
                                            "yearly": "Billed once a year."
                                        },
                                        "x-extensible-enum": true,
                                        "description": "Interval a new subscription of this type is billed at by default. Omitting it on create yields yearly, the stored default, not the monthly that plnnrly's form pre-selects, which is a form convenience the API deliberately does not reproduce."
                                    },
                                    "auto_renew": {
                                        "type": "boolean",
                                        "description": "Whether subscriptions created from this type renew automatically by default. The value is stored exactly as sent; nothing forces it to `false`."
                                    },
                                    "active": {
                                        "type": "boolean",
                                        "description": "Whether the type is offered in the catalogue. It is a catalogue flag only: nothing in the application filters on it, so deactivating a type does not stop existing subscriptions of that type from being renewed or invoiced."
                                    }
                                },
                                "required": [
                                    "name"
                                ],
                                "additionalProperties": false
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Created.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "Idempotency-Replayed": {
                                "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise.",
                                "schema": {
                                    "type": "boolean",
                                    "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise."
                                }
                            },
                            "Location": {
                                "description": "The absolute URL of the created or queued resource.",
                                "schema": {
                                    "type": "string",
                                    "description": "The absolute URL of the created or queued resource."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/SubscriptionType"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "$ref": "#/components/responses/Problem409Idempotency"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "A monthly hosting retainer type",
                        "body": {
                            "name": "Hosting retainer",
                            "description": "Managed hosting and monitoring, billed monthly.",
                            "default_billing_interval": "monthly",
                            "auto_renew": true,
                            "active": true
                        }
                    }
                ],
                "x-plnnrly-feature": "billing",
                "x-plnnrly-permission": "subscription_types::create",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:subscription_types"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/SubscriptionTypeList",
                    "detail": "#/components/schemas/SubscriptionType"
                }
            },
            "x-plnnrly-allow": [
                "GET",
                "POST"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/subscription-types/{subscription_type}": {
            "get": {
                "operationId": "api.v2.subscription-types.show",
                "summary": "Read a subscription type",
                "description": "Returns one type in the detail view. Types are soft-deleted, so a deleted type is `404` unless `trashed=with` or `trashed=only` is passed; `trashed=only` 404s a live type. Its template lines are reached through the items link, never through `?include=`.",
                "tags": [
                    "Billing"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:subscription_types"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:subscription_types"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "subscription_type",
                        "in": "path",
                        "description": "The SubscriptionType's numeric id. Accepts a soft-deleted record.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/Trashed"
                    },
                    {
                        "$ref": "#/components/parameters/Meta"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/SubscriptionType"
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/SubscriptionTypesShowMeta"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "One subscription type",
                        "path": {
                            "subscription_type": 1
                        }
                    }
                ],
                "x-plnnrly-feature": "billing",
                "x-plnnrly-permission": "subscription_types::read",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:read:subscription_types"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/SubscriptionTypeList",
                    "detail": "#/components/schemas/SubscriptionType"
                }
            },
            "patch": {
                "operationId": "api.v2.subscription-types.update",
                "summary": "Update a subscription type",
                "description": "Partially updates a type: absent keys are untouched. Send `If-Match` with the `ETag` from your last read to detect a concurrent edit, which answers `412`. Renaming a type also renames what its existing customer subscriptions read as: a subscription's displayed name is its type's name, resolved at read time rather than copied. Existing subscriptions keep the billing interval and renewal flag they were created with; a change here applies to subscriptions created afterwards.",
                "tags": [
                    "Billing"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:subscription_types"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:subscription_types"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "subscription_type",
                        "in": "path",
                        "description": "The SubscriptionType's numeric id. Accepts a soft-deleted record.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/Trashed"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "description": "Subscription type name. It is also the name every customer subscription of this type is displayed under, so renaming a type renames what its existing subscriptions read as. Names are not unique."
                                    },
                                    "description": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "description": "What subscriptions of this type cover, as free text."
                                    },
                                    "default_billing_interval": {
                                        "type": "string",
                                        "enum": [
                                            "yearly",
                                            "monthly",
                                            "one-time"
                                        ],
                                        "x-enum-descriptions": {
                                            "monthly": "Billed every month.",
                                            "one-time": "Charged once, on the invoice the product is first put on, and never renewed.",
                                            "yearly": "Billed once a year."
                                        },
                                        "x-extensible-enum": true,
                                        "description": "Interval a new subscription of this type is billed at by default. Omitting it on create yields yearly, the stored default, not the monthly that plnnrly's form pre-selects, which is a form convenience the API deliberately does not reproduce."
                                    },
                                    "auto_renew": {
                                        "type": "boolean",
                                        "description": "Whether subscriptions created from this type renew automatically by default. The value is stored exactly as sent; nothing forces it to `false`."
                                    },
                                    "active": {
                                        "type": "boolean",
                                        "description": "Whether the type is offered in the catalogue. It is a catalogue flag only: nothing in the application filters on it, so deactivating a type does not stop existing subscriptions of that type from being renewed or invoiced."
                                    }
                                },
                                "additionalProperties": false,
                                "minProperties": 1
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The updated record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/SubscriptionType"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Retire a type from the catalogue",
                        "path": {
                            "subscription_type": 1
                        },
                        "body": {
                            "active": false
                        },
                        "headers": {
                            "If-Match": "*"
                        }
                    }
                ],
                "x-plnnrly-feature": "billing",
                "x-plnnrly-permission": "subscription_types::update",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:subscription_types"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/SubscriptionTypeList",
                    "detail": "#/components/schemas/SubscriptionType"
                }
            },
            "delete": {
                "operationId": "api.v2.subscription-types.destroy",
                "summary": "Delete a subscription type",
                "description": "Soft-deletes a type: it is only marked deleted, and POST `subscription-types/{subscription_type}/actions/restore` reverses it. There is no permanent delete. Nothing else is deleted and nothing is cleaned up: existing subscriptions keep pointing at a type that has vanished from the catalogue and from `?include=subscription_type` reads, and their displayed name goes with it. The type's own template lines are not deleted either, for the same reason. Re-deleting an already-deleted type answers `204`, so a retry is safe.",
                "tags": [
                    "Billing"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:destroy:subscription_types"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "subscription_type",
                        "in": "path",
                        "description": "The SubscriptionType's numeric id. Accepts a soft-deleted record.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "responses": {
                    "204": {
                        "description": "Deleted. No body.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Delete a subscription type",
                        "path": {
                            "subscription_type": 1
                        }
                    }
                ],
                "x-plnnrly-feature": "billing",
                "x-plnnrly-permission": "subscription_types::delete",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:destroy:subscription_types"
                ]
            },
            "x-plnnrly-allow": [
                "GET",
                "PATCH",
                "DELETE"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/subscription-types/{subscription_type}/actions/restore": {
            "post": {
                "operationId": "api.v2.subscription-types.actions.restore",
                "summary": "Restore a deleted subscription type",
                "description": "Clears the soft-delete marker and returns the type in the detail view, which puts it back in the catalogue and gives its existing customer subscriptions their displayed name back. Restoring additionally requires `subscription_types::delete` as well as `subscription_types::create`. Restoring a type that is not deleted succeeds and returns an `already_in_target_state` warning. The restore finds a deleted type on its own, so the trashed parameter is not accepted. The type's template lines were never deleted, so nothing about them changes.",
                "tags": [
                    "Billing"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:subscription_types"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:subscription_types"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "subscription_type",
                        "in": "path",
                        "description": "The SubscriptionType's numeric id. Accepts a soft-deleted record.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The record after the action ran.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "Idempotency-Replayed": {
                                "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise.",
                                "schema": {
                                    "type": "boolean",
                                    "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/SubscriptionType"
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/SubscriptionTypesActionsRestoreMeta"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "$ref": "#/components/responses/Problem409Idempotency"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Restore a deleted subscription type",
                        "path": {
                            "subscription_type": 1
                        },
                        "body": {}
                    }
                ],
                "x-plnnrly-feature": "billing",
                "x-plnnrly-permission": "subscription_types::create",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:subscription_types"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/SubscriptionTypeList",
                    "detail": "#/components/schemas/SubscriptionType"
                }
            },
            "x-plnnrly-allow": [
                "POST"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/subscription-types/{subscription_type}/items": {
            "get": {
                "operationId": "api.v2.subscription-types.items.index",
                "summary": "List a subscription type's template lines",
                "description": "Returns one subscription type's template lines in template order, `sort_order` ascending. There is no organisation-wide collection of template lines and no `filter[subscription_type_id]`: a line only means anything under the type it belongs to, so this nested path is the only way to list them. A deleted type answers `404` for the whole path unless `trashed=with` is passed, which also includes the type's deleted lines. Defaults to the summary view; pass `view=detail` for the full payload, or `include=subscribable` to expand the product each line sells.",
                "tags": [
                    "Billing"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:subscription_types"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:subscription_types"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "subscription_type",
                        "in": "path",
                        "description": "The SubscriptionType's numeric id. Accepts a soft-deleted record.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/PerPage"
                    },
                    {
                        "$ref": "#/components/parameters/Cursor"
                    },
                    {
                        "$ref": "#/components/parameters/SubscriptionTypeItemPageNumber"
                    },
                    {
                        "$ref": "#/components/parameters/ViewSummary"
                    },
                    {
                        "$ref": "#/components/parameters/Trashed"
                    },
                    {
                        "name": "filter[created_at]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][gte]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][lt]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[subscribable_id]",
                        "in": "query",
                        "description": "Id of the record this template line sells. Send together with `subscribable_type`.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        },
                        "x-plnnrly-constraint": "Send subscribable_id and subscribable_type together."
                    },
                    {
                        "name": "filter[subscribable_id][in]",
                        "in": "query",
                        "description": "Id of the record this template line sells. Send together with `subscribable_type`. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false,
                        "x-plnnrly-constraint": "Send subscribable_id and subscribable_type together."
                    },
                    {
                        "name": "filter[subscribable_id][null]",
                        "in": "query",
                        "description": "Id of the record this template line sells. Send together with `subscribable_type`. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        },
                        "x-plnnrly-constraint": "Send subscribable_id and subscribable_type together."
                    },
                    {
                        "name": "filter[subscribable_type]",
                        "in": "query",
                        "description": "Type of the record this template line sells. Send together with `subscribable_id`.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "x-extensible-enum": true
                        },
                        "x-plnnrly-constraint": "Send subscribable_type and subscribable_id together."
                    },
                    {
                        "name": "filter[updated_at]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][gte]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][lt]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/SubscriptionTypeItemSort"
                    },
                    {
                        "name": "q",
                        "in": "query",
                        "description": "Free-text search over `name`. At most one substring match per request: `q` together with a `contains` filter is 400 invalid-query-parameter.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "minLength": 1
                        }
                    },
                    {
                        "$ref": "#/components/parameters/SubscriptionTypeItemInclude"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "A page of records.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record.",
                                "schema": {
                                    "type": "string",
                                    "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/SubscriptionTypeItemList"
                                            }
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/SubscriptionTypesItemsIndexMeta"
                                        },
                                        "links": {
                                            "$ref": "#/components/schemas/PageLinks"
                                        }
                                    },
                                    "required": [
                                        "data",
                                        "meta",
                                        "links"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "One type's template lines, including its deleted ones",
                        "path": {
                            "subscription_type": 1
                        },
                        "query": {
                            "trashed": "with"
                        }
                    }
                ],
                "x-plnnrly-feature": "billing",
                "x-plnnrly-permission": "subscription_types::read",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1,
                    "view_detail_total": 3,
                    "surcharges": {
                        "include": 1,
                        "q": 2
                    }
                },
                "x-plnnrly-token-ability": [
                    "api:read:subscription_types"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/SubscriptionTypeItemList",
                    "detail": "#/components/schemas/SubscriptionTypeItem"
                }
            },
            "post": {
                "operationId": "api.v2.subscription-types.items.store",
                "summary": "Add a template line to a subscription type",
                "description": "Creates one template line on the subscription type named in the path and returns it in the detail view, with a `Location` header pointing at the flat item path. The type comes from the path, so `subscription_type_id` is not accepted in the body and a line can never be moved between types. Every field is optional: a POST with an empty body creates a blank line, exactly as plnnrly's form does when a line is added. `subscribable_type` and `subscribable_id` are all-or-nothing and the only accepted type is `subscription_product`: a line can be pointed at a hosting domain or server in plnnrly, and a GET will report that, but not through this API. Nothing on the subscription type itself is recalculated: a type is a blueprint and has no totals. A deleted type answers `404`.",
                "tags": [
                    "Billing"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:subscription_types"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:subscription_types"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "subscription_type",
                        "in": "path",
                        "description": "The SubscriptionType's numeric id. Accepts a soft-deleted record.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 255,
                                        "description": "Optional label for the template line. plnnrly's form does not show it, so most existing lines carry `null`; it exists for programmatic naming and is what `?q=` searches."
                                    },
                                    "description": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "description": "Invoice-line text template, copied verbatim onto the customer subscription's line and resolved only when that line is invoiced. Placeholder tokens survive the copy unresolved: {{$domain}}, {{$period}}, {{$product}}, {{$customer}}, {{$site}}, {{$server}}, plus the Dutch aliases {{$domein}}, {{$periode}} and {{$klant}}."
                                    },
                                    "quantity": {
                                        "type": "integer",
                                        "minimum": 1,
                                        "description": "Number of units the template line carries. Whole units: this is an integer, not the fractional quantity an invoice row uses."
                                    },
                                    "default_purchase_price": {
                                        "type": "string",
                                        "format": "decimal",
                                        "pattern": "^-?\\d+(\\.\\d{1,2})?$",
                                        "x-plnnrly-constraint": "At least -99999999.99. At most 99999999.99.",
                                        "x-plnnrly-maximum": 99999999.99,
                                        "x-plnnrly-minimum": -99999999.99,
                                        "description": "Purchase price per unit, excluding VAT, copied onto a new customer subscription's line."
                                    },
                                    "default_sales_price": {
                                        "type": "string",
                                        "format": "decimal",
                                        "pattern": "^-?\\d+(\\.\\d{1,2})?$",
                                        "x-plnnrly-constraint": "At least -99999999.99. At most 99999999.99.",
                                        "x-plnnrly-maximum": 99999999.99,
                                        "x-plnnrly-minimum": -99999999.99,
                                        "description": "Sales price per unit, excluding VAT, copied onto a new customer subscription's line. This is what eventually bills."
                                    },
                                    "vat_rate": {
                                        "type": "integer",
                                        "minimum": 0,
                                        "maximum": 100,
                                        "description": "VAT percentage as a whole number: 21 means 21%."
                                    },
                                    "subscribable_type": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "enum": [
                                            "subscription_product",
                                            null
                                        ],
                                        "x-plnnrly-constraint": "Required when subscribable_id is present.",
                                        "description": "The record this template line sells, normally a subscription product. Null on a line that sells nothing in particular, and a bare {\"id\": N} reference with `subscribable_type` of `domain` or `server` on a line pointed at a hosting record, which only plnnrly itself can do. Expanding it with `?include=subscribable` additionally requires `subscription_products` read permission and a token scope covering that prefix."
                                    },
                                    "subscribable_id": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "x-plnnrly-constraint": "Required when subscribable_type is present.",
                                        "x-plnnrly-references": {
                                            "resource": "SubscriptionProduct",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "subscription_products",
                                        "description": "The record this template line sells, normally a subscription product. Null on a line that sells nothing in particular, and a bare {\"id\": N} reference with `subscribable_type` of `domain` or `server` on a line pointed at a hosting record, which only plnnrly itself can do. Expanding it with `?include=subscribable` additionally requires `subscription_products` read permission and a token scope covering that prefix."
                                    }
                                },
                                "additionalProperties": false,
                                "minProperties": 1
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Created.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "Idempotency-Replayed": {
                                "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise.",
                                "schema": {
                                    "type": "boolean",
                                    "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise."
                                }
                            },
                            "Location": {
                                "description": "The absolute URL of the created or queued resource.",
                                "schema": {
                                    "type": "string",
                                    "description": "The absolute URL of the created or queued resource."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/SubscriptionTypeItem"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "$ref": "#/components/responses/Problem409Idempotency"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "A template line selling one subscription product",
                        "path": {
                            "subscription_type": 1
                        },
                        "body": {
                            "name": "Managed hosting",
                            "description": "Hosting for {{$domain}} over {{$period}}.",
                            "quantity": 1,
                            "default_purchase_price": "12.50",
                            "default_sales_price": "29.95",
                            "subscribable_type": "subscription_product",
                            "subscribable_id": 1
                        }
                    }
                ],
                "x-plnnrly-feature": "billing",
                "x-plnnrly-permission": "subscription_types::create",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:subscription_types"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/SubscriptionTypeItemList",
                    "detail": "#/components/schemas/SubscriptionTypeItem"
                }
            },
            "x-plnnrly-allow": [
                "GET",
                "POST"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/subtasks/{subtask}": {
            "get": {
                "operationId": "api.v2.subtasks.show",
                "summary": "Read a subtask",
                "description": "Returns one checklist subtask in the detail view, including its rich-text `description`. Subtasks are hard-deleted, so a deleted one is simply 404 and `trashed` is not an accepted parameter. Reading additionally requires `project_management::manage_work_items` on the parent work item.",
                "tags": [
                    "Work items"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:project_management"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:project_management"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "subtask",
                        "in": "path",
                        "description": "The Subtask's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Subtask"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "One subtask with its work item expanded",
                        "path": {
                            "subtask": 1
                        },
                        "query": {
                            "include": "work_item"
                        }
                    }
                ],
                "x-plnnrly-permission": "project_management::view_backlog",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:read:project_management"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/SubtaskList",
                    "detail": "#/components/schemas/Subtask"
                }
            },
            "patch": {
                "operationId": "api.v2.subtasks.update",
                "summary": "Update a subtask",
                "description": "Partially updates one subtask: absent keys are untouched. This covers both plnnrly's edit action (title and description) and its tick-off toggle (`is_resolved`); there is no actions/resolve action. Send `If-Match` with the `ETag` from your last read to detect a concurrent edit, which answers `412`; a subtask has no `version` field, so its `ETag` is derived from `updated_at`, and the `ETag` in the response is the one to send next. `work_item_id` and order are not accepted: a subtask cannot be moved to another work item, and there is no way to reorder a checklist through the API. Note the API is stricter than plnnrly here: plnnrly lets a user merely assigned to a work item tick its subtasks, while this endpoint requires `project_management::manage_work_items`.",
                "tags": [
                    "Work items"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:project_management"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:project_management"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "subtask",
                        "in": "path",
                        "description": "The Subtask's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "title": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "description": "Subtask title, as shown on the checklist row. This is the field `?q=` searches."
                                    },
                                    "description": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "description": "Optional note on the subtask. HTML from the rich editor, sanitised on read and on write. Null when the subtask is just a title."
                                    },
                                    "is_resolved": {
                                        "type": "boolean",
                                        "description": "Whether the subtask is ticked off."
                                    }
                                },
                                "required": [
                                    "title"
                                ],
                                "additionalProperties": false,
                                "minProperties": 1
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The updated record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Subtask"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Tick a subtask off",
                        "path": {
                            "subtask": 1
                        },
                        "body": {
                            "is_resolved": true
                        },
                        "headers": {
                            "If-Match": "*"
                        }
                    }
                ],
                "x-plnnrly-permission": "project_management::manage_work_items",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:project_management"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/SubtaskList",
                    "detail": "#/components/schemas/Subtask"
                }
            },
            "delete": {
                "operationId": "api.v2.subtasks.destroy",
                "summary": "Delete a subtask",
                "description": "Permanently deletes one checklist subtask and answers `204`. There is no soft delete, no ?force parameter and no restore verb: a subtask comes back only by being re-created through `POST work-items/{work_item}/subtasks`, and it is appended to the end of the checklist rather than to its old position. Nothing else is deleted, except that any files attached to the subtask in plnnrly are removed with it.",
                "tags": [
                    "Work items"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:destroy:project_management"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "subtask",
                        "in": "path",
                        "description": "The Subtask's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "responses": {
                    "204": {
                        "description": "Deleted. No body.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Remove a subtask",
                        "path": {
                            "subtask": 1
                        }
                    }
                ],
                "x-plnnrly-permission": "project_management::manage_work_items",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:destroy:project_management"
                ]
            },
            "x-plnnrly-allow": [
                "GET",
                "PATCH",
                "DELETE"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/tasks/{task}": {
            "get": {
                "operationId": "api.v2.tasks.show",
                "summary": "Read a task",
                "description": "Returns one kanban task with its budget and the employee it belongs to. Tasks are per-user: a caller sees only their own unless they hold `kanban::manage_for_others`, and an id outside that scope is `404` rather than `403`, so the endpoint cannot be walked to discover a colleague's cards. Tasks are hard-deleted, so a deleted one is simply 404 and `trashed` is not an accepted parameter. The task's project is not in the payload (a task has no `project_id` of its own, and the budget object carries only id and description), so a client that needs the project follows `GET /api/v2/budgets/{budget}`. The summary and detail views are identical on this resource.",
                "tags": [
                    "Projects"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:kanban"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:kanban"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "task",
                        "in": "path",
                        "description": "The Task's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Task"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "One task",
                        "path": {
                            "task": 1
                        }
                    }
                ],
                "x-plnnrly-permission": "kanban::manage_self",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:read:kanban"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/TaskList",
                    "detail": "#/components/schemas/Task"
                }
            },
            "patch": {
                "operationId": "api.v2.tasks.update",
                "summary": "Update a task",
                "description": "Partially updates a task: absent keys are untouched and an empty body is rejected. A status change is a plain PATCH, not an action. Note that the response body can differ from the request body: `status` and `is_finished` are cross-synced on every write, so sending `is_finished`: `true` answers status: \"finished\", moving `status` off finished answers `is_finished`: `false`, and re-opening a finished task without naming a status answers status: \"active\". `status` wins when both are sent. `budget_id` is accepted at create time only, so a task cannot be re-budgeted or moved to another project here; `user_id`, `team_id` and `order_column` are not writable at all, which means a task cannot be reassigned to another employee through the API and there is no way to reorder a board. The write also bumps the owning project's `updated_at`, so the project's `ETag` changes and it reappears in an incremental `filter[updated_at][gte]` window, and it lands in that project's activity timeline rather than a timeline of its own. Send `If-Match` with the `ETag` from your last read to detect a concurrent edit, which answers `412`.",
                "tags": [
                    "Projects"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:kanban"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:kanban"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "task",
                        "in": "path",
                        "description": "The Task's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "due_date": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "format": "date",
                                        "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                                        "description": "Day the task is due. Required when creating a task through the API, and clearable afterwards; tasks created in plnnrly may carry `null`."
                                    },
                                    "minutes": {
                                        "type": "integer",
                                        "minimum": 0,
                                        "description": "Estimated effort, in minutes."
                                    },
                                    "description": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "description": "Short description shown on the kanban card. This is the task's label and the only field `?q=` searches."
                                    },
                                    "comment": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 255,
                                        "description": "Optional longer comment on the card, capped at 255 characters."
                                    },
                                    "status": {
                                        "type": "string",
                                        "enum": [
                                            "on-hold",
                                            "new",
                                            "active",
                                            "internal-test",
                                            "external-test",
                                            "finished"
                                        ],
                                        "x-enum-descriptions": {
                                            "active": "Being worked on.",
                                            "external-test": "With the customer for acceptance.",
                                            "finished": "Done. The only status for which `is_finished` is true.",
                                            "internal-test": "Awaiting internal review or testing.",
                                            "new": "The default for a new card: not picked up yet.",
                                            "on-hold": "Paused, waiting on something off the board."
                                        },
                                        "x-extensible-enum": true,
                                        "description": "Kanban column the card sits in."
                                    },
                                    "is_finished": {
                                        "type": "boolean",
                                        "description": "Whether the task is done. Cross-synced with `status` on every write: setting `status` to finished sets this `true` and any other status sets it `false`, while setting this `false` on a finished task moves `status` back to active. `status` wins when both are sent."
                                    },
                                    "is_billable": {
                                        "type": "boolean",
                                        "description": "Whether time logged on this task is billable. Defaults to `true` when a task is created through the API, regardless of the budget's own setting."
                                    }
                                },
                                "additionalProperties": false,
                                "minProperties": 1
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The updated record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Task"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Move a card to the finished column",
                        "path": {
                            "task": 1
                        },
                        "body": {
                            "status": "finished"
                        },
                        "headers": {
                            "If-Match": "*"
                        }
                    }
                ],
                "x-plnnrly-permission": "kanban::manage_self",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:kanban"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/TaskList",
                    "detail": "#/components/schemas/Task"
                }
            },
            "delete": {
                "operationId": "api.v2.tasks.destroy",
                "summary": "Delete a task",
                "description": "Deletes a task permanently and answers `204`: it uses no soft deletes, so there is no restore verb and no force parameter, and a deleted task comes back only by being re-created through `POST projects/{project}/tasks`, appended to the end of the board rather than to its old position. A task carrying logged time is deleted without complaint: there is no guard on this operation. Nothing is deleted with it either: worklogs, planning events, Outlook events and work items that pointed at the task survive and are detached, their task reference set to `null`. The delete is recorded in the owning project's activity timeline rather than a timeline of its own, but unlike an update it does not bump the project's `updated_at`, so an incremental `filter[updated_at][gte]` window on projects will not show it.",
                "tags": [
                    "Projects"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:destroy:kanban"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "task",
                        "in": "path",
                        "description": "The Task's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "responses": {
                    "204": {
                        "description": "Deleted. No body.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Delete a task",
                        "path": {
                            "task": 1
                        }
                    }
                ],
                "x-plnnrly-permission": "kanban::manage_self",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:destroy:kanban"
                ]
            },
            "x-plnnrly-allow": [
                "GET",
                "PATCH",
                "DELETE"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/timers": {
            "get": {
                "operationId": "api.v2.timers.index",
                "summary": "List running timers",
                "description": "Returns the running timers, newest first by `created_at`. A caller sees only their own rows unless they hold `times::update_others`, and that narrowing is applied before any client filter, before pagination and before the count. `meta.page.total` is always present, so `with_total` is not an accepted parameter. Neither is `q` (a timer has no text to search), nor `meta`, `trashed` or `force`; sending any of them is a `400`. To find the timer on one record, send the type and id together: `filter[timeable_type]=work_item&filter[timeable_id]=41`; a lone half is a `400`. A timer on a record type this API does not publish (an Outlook calendar event, which the agenda really does time) is returned with `timeable_type` set to `outlook_event` and `timeable` as the bare `{\"id\": N}` reference form, which `?include=timeable` cannot expand.",
                "tags": [
                    "Time"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:hours"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:hours"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/PerPage"
                    },
                    {
                        "$ref": "#/components/parameters/Cursor"
                    },
                    {
                        "$ref": "#/components/parameters/PageNumber"
                    },
                    {
                        "$ref": "#/components/parameters/ViewSummary"
                    },
                    {
                        "name": "filter[created_at]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][gte]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][lt]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[started_at]",
                        "in": "query",
                        "description": "When the stopwatch was started.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[started_at][gte]",
                        "in": "query",
                        "description": "When the stopwatch was started.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[started_at][lte]",
                        "in": "query",
                        "description": "When the stopwatch was started.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[timeable_id]",
                        "in": "query",
                        "description": "Id of the timed record. Send together with `timeable_type`.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        },
                        "x-plnnrly-constraint": "Send timeable_id and timeable_type together."
                    },
                    {
                        "name": "filter[timeable_id][in]",
                        "in": "query",
                        "description": "Id of the timed record. Send together with `timeable_type`. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false,
                        "x-plnnrly-constraint": "Send timeable_id and timeable_type together."
                    },
                    {
                        "name": "filter[timeable_type]",
                        "in": "query",
                        "description": "Type of the timed record. Send together with `timeable_id`.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "x-extensible-enum": true
                        },
                        "x-plnnrly-constraint": "Send timeable_type and timeable_id together."
                    },
                    {
                        "name": "filter[updated_at]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][gte]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][lt]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[user_id]",
                        "in": "query",
                        "description": "Whose timers to return. Accepts the literal `me`, and defaults to the authenticated user. Naming another employee requires the `times::update_others` permission.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[user_id][in]",
                        "in": "query",
                        "description": "Whose timers to return. Accepts the literal `me`, and defaults to the authenticated user. Naming another employee requires the `times::update_others` permission. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "$ref": "#/components/parameters/TimerSort"
                    },
                    {
                        "$ref": "#/components/parameters/TimerInclude"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "A page of records.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record.",
                                "schema": {
                                    "type": "string",
                                    "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/TimerList"
                                            }
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/TimersIndexMeta"
                                        },
                                        "links": {
                                            "$ref": "#/components/schemas/PageLinks"
                                        }
                                    },
                                    "required": [
                                        "data",
                                        "meta",
                                        "links"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "My running timers, with what they are timing",
                        "query": {
                            "filter[user_id]": "me",
                            "include": "timeable"
                        }
                    },
                    {
                        "name": "The timer on one work item, oldest first",
                        "query": {
                            "filter[timeable_type]": "work_item",
                            "filter[timeable_id]": "41",
                            "sort": "started_at"
                        }
                    }
                ],
                "x-plnnrly-permission": "hours::read",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1,
                    "view_detail_total": 3,
                    "surcharges": {
                        "include": 1
                    }
                },
                "x-plnnrly-token-ability": [
                    "api:read:hours"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/TimerList",
                    "detail": "#/components/schemas/Timer"
                }
            },
            "post": {
                "operationId": "api.v2.timers.store",
                "summary": "Start a timer",
                "description": "Starts a stopwatch on a work item or a planning event for the authenticated user and returns it with a `Location` header. `user_id` is not accepted: a timer always belongs to the caller, and there is no way to start one for another employee. Send `timeable_type` and `timeable_id` together; omit `started_at` to start now. A second timer on the same record for the same caller is a `422` with code `unique` (there is at most one timer per employee per record), so `Idempotency-Key` is optional here rather than required: a retry meets that rule anyway. There is no stop action: to book the elapsed minutes, read `started_at`, POST the hours to `/api/v2/worklogs` and then DELETE this timer. Those two calls are not atomic: a client that deletes first loses the minutes, and one that crashes between them leaves the timer running. This operation declares no permission of its own, and a caller can only ever create their own timer. plnnrly also hides its Start button on a record you may not log time on; the API does not check that when the timer starts. Nothing unbillable can be booked that way regardless: `POST /api/v2/worklogs` applies that gate when the hours are actually written.",
                "tags": [
                    "Time"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:hours"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:hours"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "timeable_type": {
                                        "type": "string",
                                        "enum": [
                                            "work_item",
                                            "event"
                                        ],
                                        "description": "The record being timed. Expanding it with `?include=timeable` additionally requires the read permission and token scope of the target's own prefix, per branch: `project_management` for a work item, events for a planning event."
                                    },
                                    "timeable_id": {
                                        "type": "integer",
                                        "x-plnnrly-constraint": "Required when timeable_type is present. Must be unique across Timer.",
                                        "x-plnnrly-references": {
                                            "resource": "WorkItem",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "project_management",
                                        "x-plnnrly-unique": {
                                            "resource": "Timer",
                                            "field": "timeable_id"
                                        },
                                        "description": "The record being timed. Expanding it with `?include=timeable` additionally requires the read permission and token scope of the target's own prefix, per branch: `project_management` for a work item, events for a planning event."
                                    },
                                    "started_at": {
                                        "type": "string",
                                        "format": "date-time",
                                        "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$",
                                        "description": "When the stopwatch was started. Elapsed time is this value against the client's own clock; the API publishes no elapsed counter."
                                    }
                                },
                                "required": [
                                    "timeable_type"
                                ],
                                "additionalProperties": false
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Created.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "Idempotency-Replayed": {
                                "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise.",
                                "schema": {
                                    "type": "boolean",
                                    "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise."
                                }
                            },
                            "Location": {
                                "description": "The absolute URL of the created or queued resource.",
                                "schema": {
                                    "type": "string",
                                    "description": "The absolute URL of the created or queued resource."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Timer"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "$ref": "#/components/responses/Problem409Idempotency"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Start timing a work item now",
                        "body": {
                            "timeable_type": "work_item",
                            "timeable_id": 41
                        }
                    },
                    {
                        "name": "Start timing a planning event from a known instant",
                        "body": {
                            "timeable_type": "event",
                            "timeable_id": 7,
                            "started_at": "2026-09-01T09:00:00Z"
                        }
                    }
                ],
                "x-plnnrly-permission": null,
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:hours"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/TimerList",
                    "detail": "#/components/schemas/Timer"
                }
            },
            "x-plnnrly-allow": [
                "GET",
                "POST"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/timers/{timer}": {
            "get": {
                "operationId": "api.v2.timers.show",
                "summary": "Read a timer",
                "description": "Returns one timer. The summary and detail views are identical on this resource: every field is small enough for a list row. A timer belonging to another employee is `404` unless the caller holds `times::update_others`; the row is invisible, so its existence stays invisible. `user` and `timeable` render as `{\"id\": N}` references; `?include=timeable,user` expands them, and each expansion additionally requires the read permission and token scope of its own target prefix. A timer on an Outlook calendar event keeps the bare `{\"id\": N}` form with `timeable_type` intact even under `?include=timeable`, because that record type is not published by this API. Elapsed time is derived from `started_at` by the client; the API publishes no elapsed counter.",
                "tags": [
                    "Time"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:hours"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:hours"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "timer",
                        "in": "path",
                        "description": "The Timer's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Timer"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "One timer with its target and owner expanded",
                        "path": {
                            "timer": 1
                        },
                        "query": {
                            "include": "timeable,user"
                        }
                    }
                ],
                "x-plnnrly-permission": "hours::read",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:read:hours"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/TimerList",
                    "detail": "#/components/schemas/Timer"
                }
            },
            "patch": {
                "operationId": "api.v2.timers.update",
                "summary": "Correct a timer",
                "description": "Partially updates a timer: absent keys are untouched, an empty body is rejected. `started_at` is the only writable key; correcting a mis-started stopwatch is the whole purpose. The `timeable_type` / `timeable_id` pair is never accepted on a PATCH, so retargeting a timer is a DELETE followed by a POST. `user_id` is not accepted either, so a timer cannot be moved to another employee. Send `If-Match` with the `ETag` from your last read to detect a concurrent edit, which answers `412`. Treat it as best-effort only: this resource carries no concurrency token, and `updated_at` is second-precision, so two writes inside the same second are indistinguishable. This operation declares no permission of its own: you may change your own timers, and changing another employee's timer additionally requires `times::update_others`.",
                "tags": [
                    "Time"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:hours"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:hours"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "timer",
                        "in": "path",
                        "description": "The Timer's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "started_at": {
                                        "type": "string",
                                        "format": "date-time",
                                        "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$",
                                        "description": "When the stopwatch was started. Elapsed time is this value against the client's own clock; the API publishes no elapsed counter."
                                    }
                                },
                                "additionalProperties": false,
                                "minProperties": 1
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The updated record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Timer"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Move the start back to when the work really began",
                        "path": {
                            "timer": 1
                        },
                        "body": {
                            "started_at": "2026-09-01T08:45:00Z"
                        },
                        "headers": {
                            "If-Match": "*"
                        }
                    }
                ],
                "x-plnnrly-permission": null,
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:hours"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/TimerList",
                    "detail": "#/components/schemas/Timer"
                }
            },
            "delete": {
                "operationId": "api.v2.timers.destroy",
                "summary": "Discard a timer",
                "description": "Permanently deletes a timer, like discarding it in plnnrly. This resource is hard-delete: there is no restore action, no `force` parameter, no `trashed` parameter and no deletion signal for a sync client. Nothing blocks the delete, and the elapsed minutes are simply lost: to keep them, POST the hours to `/api/v2/worklogs` first and delete the timer afterwards. Those two calls are not atomic: deleting first loses the minutes, and crashing between them leaves a stale timer running. This operation declares no permission of its own: you may delete your own timers, and deleting another employee's timer additionally requires `times::update_others`.",
                "tags": [
                    "Time"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:destroy:hours"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "timer",
                        "in": "path",
                        "description": "The Timer's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "responses": {
                    "204": {
                        "description": "Deleted. No body.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Discard a timer",
                        "path": {
                            "timer": 1
                        }
                    }
                ],
                "x-plnnrly-permission": null,
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:destroy:hours"
                ]
            },
            "x-plnnrly-allow": [
                "GET",
                "PATCH",
                "DELETE"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/todos": {
            "get": {
                "operationId": "api.v2.todos.index",
                "summary": "List your todos",
                "description": "Returns your todos (the unchecked task-list lines in drive documents that mention you), newest first. This is the list behind the Todos dashboard in plnnrly. The collection is hard-scoped to the authenticated caller with no escape: there is no permission that widens it, `filter[user_id]` accepts `me` and nothing else, and no include reaches another user's rows. ⚠ Rows are auto-derived from document content: every write to a drive item deletes the whole todo set for that item and rebuilds it from the document HTML, so ids are not stable across an edit of a source document. `?q=` substring-matches `todo_content`, and `filter[drive_item_id]` narrows to particular documents. `total` is omitted unless you pass `with_total=1`.",
                "tags": [
                    "Drive"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:project_management"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:project_management"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/PerPage"
                    },
                    {
                        "$ref": "#/components/parameters/Cursor"
                    },
                    {
                        "$ref": "#/components/parameters/ViewSummary"
                    },
                    {
                        "$ref": "#/components/parameters/WithTotal"
                    },
                    {
                        "name": "filter[created_at]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][gte]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][lt]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[drive_item_id]",
                        "in": "query",
                        "description": "Limit to todos parsed out of these drive items.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[drive_item_id][in]",
                        "in": "query",
                        "description": "Limit to todos parsed out of these drive items. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[updated_at]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][gte]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][lt]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[user_id]",
                        "in": "query",
                        "description": "Whose todos to return. Accepts the literal `me`, and `me` is the only value any caller may name: this collection has no permission that grants access to another user's todos.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[user_id][in]",
                        "in": "query",
                        "description": "Whose todos to return. Accepts the literal `me`, and `me` is the only value any caller may name: this collection has no permission that grants access to another user's todos. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "$ref": "#/components/parameters/UserTodoSort"
                    },
                    {
                        "name": "q",
                        "in": "query",
                        "description": "Free-text search over `todo_content`. At most one substring match per request: `q` together with a `contains` filter is 400 invalid-query-parameter.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "minLength": 1
                        }
                    },
                    {
                        "$ref": "#/components/parameters/UserTodoInclude"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "A page of records.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record.",
                                "schema": {
                                    "type": "string",
                                    "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/UserTodoList"
                                            }
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/TodosIndexMeta"
                                        },
                                        "links": {
                                            "$ref": "#/components/schemas/PageLinks"
                                        }
                                    },
                                    "required": [
                                        "data",
                                        "meta",
                                        "links"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Your open todos from one document",
                        "query": {
                            "filter": {
                                "drive_item_id": 1
                            },
                            "include": "user",
                            "with_total": 1
                        }
                    }
                ],
                "x-plnnrly-permission": "project_management::view_backlog",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1,
                    "view_detail_total": 3,
                    "surcharges": {
                        "with_total": 2,
                        "include": 1,
                        "q": 2
                    }
                },
                "x-plnnrly-token-ability": [
                    "api:read:project_management"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/UserTodoList",
                    "detail": "#/components/schemas/UserTodo"
                }
            },
            "post": {
                "operationId": "api.v2.todos.store",
                "summary": "Create a todo",
                "description": "Creates a todo for you against a drive item and returns it with a `Location` header. `user_id` is always the authenticated caller and cannot be set: there is no way to create a todo for someone else. A drive item you cannot read, or one that is archived, is refused exactly as a nonexistent one is. ⚠ This write is temporary by construction. Todos are a derived copy of document content: the next write to the source drive item (including a title-only `PATCH /api/v2/drive-items/{drive_item}`) deletes every todo for that item and rebuilds the set from the document HTML, so a todo created here disappears the next time its document is touched. To create a todo that lasts, add an unchecked task item mentioning the user to the document itself.",
                "tags": [
                    "Drive"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:project_management"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:project_management"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "drive_item_id": {
                                        "type": "integer",
                                        "x-plnnrly-references": {
                                            "resource": "DriveItem",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "project_drive",
                                        "description": "The drive item whose document this todo was parsed out of. Writable on create only; a todo cannot be re-parented. No relation object is exposed for it; read the document with `GET /api/v2/drive-items/{drive_item}`, which applies the drive access rules this collection does not. Archiving a drive item leaves its todos behind, so this id can name an item you can no longer read."
                                    },
                                    "todo_content": {
                                        "type": "string",
                                        "maxLength": 65535,
                                        "description": "The todo line as plain text."
                                    }
                                },
                                "required": [
                                    "drive_item_id",
                                    "todo_content"
                                ],
                                "additionalProperties": false
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Created.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "Idempotency-Replayed": {
                                "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise.",
                                "schema": {
                                    "type": "boolean",
                                    "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise."
                                }
                            },
                            "Location": {
                                "description": "The absolute URL of the created or queued resource.",
                                "schema": {
                                    "type": "string",
                                    "description": "The absolute URL of the created or queued resource."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/UserTodo"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "$ref": "#/components/responses/Problem409Idempotency"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "A todo against a document",
                        "body": {
                            "drive_item_id": 1,
                            "todo_content": "Confirm the kick-off date with the client"
                        }
                    }
                ],
                "x-plnnrly-permission": "project_management::manage_work_items",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:project_management"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/UserTodoList",
                    "detail": "#/components/schemas/UserTodo"
                }
            },
            "x-plnnrly-allow": [
                "GET",
                "POST"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/todos/{todo}": {
            "get": {
                "operationId": "api.v2.todos.show",
                "summary": "Read a todo",
                "description": "Returns one of your todos in the detail view. The summary and detail payloads are identical on this resource: `todo_content` is rendered in both, because the collection is the surface this resource exists for. `user` renders as {\"id\": N} until named in include, and no relation object is exposed for the source document: read it with GET `/api/v2/drive-items/{drive_item}`, which applies the drive access rules this collection does not. Another user's todo answers `404`, never `403`. There is no soft delete: a deleted todo is gone, and `trashed` is not an accepted parameter.",
                "tags": [
                    "Drive"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:project_management"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:project_management"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "todo",
                        "in": "path",
                        "description": "The UserTodo's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/UserTodo"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "One todo with its user expanded",
                        "path": {
                            "todo": 1
                        },
                        "query": {
                            "include": "user"
                        }
                    }
                ],
                "x-plnnrly-permission": "project_management::view_backlog",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:read:project_management"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/UserTodoList",
                    "detail": "#/components/schemas/UserTodo"
                }
            },
            "patch": {
                "operationId": "api.v2.todos.update",
                "summary": "Update a todo",
                "description": "Partially updates one of your todos: absent keys are untouched, an empty body is rejected. `todo_content` is the only writable key: a todo cannot be re-parented, so `drive_item_id` is not accepted, and `user_id` is never writable. Send `If-Match` with the `ETag` from your last read to detect a concurrent edit, which answers `412`. Treat it as best-effort only: this resource carries no concurrency token and `updated_at` is second-precision, so two writes inside the same second are indistinguishable. ⚠ This write is temporary by construction. Todos are a derived copy of document content: the next write to the source drive item (including a title-only `PATCH /api/v2/drive-items/{drive_item}`) deletes every todo for that item and rebuilds the set from the document HTML, so this edit is discarded the next time the document is touched. To change a todo's text durably, edit the task item in the document itself.",
                "tags": [
                    "Drive"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:project_management"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:project_management"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "todo",
                        "in": "path",
                        "description": "The UserTodo's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "todo_content": {
                                        "type": "string",
                                        "maxLength": 65535,
                                        "description": "The todo line as plain text."
                                    }
                                },
                                "additionalProperties": false,
                                "minProperties": 1
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The updated record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/UserTodo"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Rewrite the todo line",
                        "path": {
                            "todo": 1
                        },
                        "body": {
                            "todo_content": "Confirm the kick-off date with the client before Friday"
                        },
                        "headers": {
                            "If-Match": "*"
                        }
                    }
                ],
                "x-plnnrly-permission": "project_management::manage_work_items",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:project_management"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/UserTodoList",
                    "detail": "#/components/schemas/UserTodo"
                }
            },
            "delete": {
                "operationId": "api.v2.todos.destroy",
                "summary": "Delete a todo",
                "description": "Permanently deletes one of your todos. This resource is hard-delete: there is no restore action, no `force` parameter and no `trashed` parameter. Deleting an already-deleted todo answers `404`, and the source document is unaffected. ⚠ The DELETE is cosmetic and temporary. Todos are a derived copy of document content: the todo comes back the next time the source drive item is written and its todo set rebuilt, for as long as the unchecked task item mentioning you is still in the document HTML. To make a todo go away for good, check the task item off in the document, or remove the mention.",
                "tags": [
                    "Drive"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:destroy:project_management"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "todo",
                        "in": "path",
                        "description": "The UserTodo's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "responses": {
                    "204": {
                        "description": "Deleted. No body.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Remove a todo",
                        "path": {
                            "todo": 1
                        }
                    }
                ],
                "x-plnnrly-permission": "project_management::manage_work_items",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:destroy:project_management"
                ]
            },
            "x-plnnrly-allow": [
                "GET",
                "PATCH",
                "DELETE"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/uploads/{upload}": {
            "get": {
                "operationId": "api.v2.uploads.show",
                "summary": "Read an upload ticket",
                "description": "Returns one of your own upload tickets: its status, the size and SHA-256 of the bytes the server actually received, the media type it detected from them, both deadlines and, while the ticket is still waiting for its bytes, the `upload_url` to PUT them to. Use it to check whether an upload landed before registering it, or to recover the `media_id` of one already registered. A ticket belongs to the credential that created it: somebody else's ticket is `404`, and so is one whose permission family your token does not cover. No domain permission is required (the ticket is your own scratch object), but the parent record's write permission was checked when it was created and is checked again when it is registered. Tickets are hard-deleted, so `trashed` is not an accepted parameter, and a pruned one is `404`.",
                "tags": [
                    "Start here"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:project_management"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:read:project_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:read:customer_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:read:organization_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:read:user_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:project_management"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:project_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:customer_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:organization_drive"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:user_drive"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "upload",
                        "in": "path",
                        "description": "The Upload's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Upload"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Check whether an upload landed",
                        "path": {
                            "upload": 1
                        }
                    }
                ],
                "x-plnnrly-permission": null,
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:read:project_management",
                    "api:read:project_drive",
                    "api:read:customer_drive",
                    "api:read:organization_drive",
                    "api:read:user_drive"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/UploadList",
                    "detail": "#/components/schemas/Upload"
                }
            },
            "delete": {
                "operationId": "api.v2.uploads.destroy",
                "summary": "Delete an upload ticket",
                "description": "Revokes one of your own unregistered upload tickets: the staged bytes are deleted from storage, the ticket is marked revoked and answers `204`. This is the escape hatch for a ticket whose `upload_url` leaked, and it is why a 30-minute window is acceptable rather than merely short. A ticket that has already become a file is `409` `upload_already_registered`: the bytes are an attachment now, and this API does not delete files; remove one in plnnrly. Somebody else's ticket is `404`, as is one whose permission family your token does not cover. No domain permission is required. There is no soft delete, no ?force and no restore verb.",
                "tags": [
                    "Start here"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:destroy:project_management"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:destroy:project_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:destroy:customer_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:destroy:organization_drive"
                        ]
                    },
                    {
                        "bearerToken": [
                            "api:destroy:user_drive"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "upload",
                        "in": "path",
                        "description": "The Upload's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "responses": {
                    "204": {
                        "description": "Deleted. No body.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "description": "Conflict.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/StateConflictProblem"
                                },
                                "examples": {
                                    "state-conflict": {
                                        "$ref": "#/components/examples/StateConflictProblem"
                                    }
                                }
                            }
                        }
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Abandon a staged upload",
                        "path": {
                            "upload": 1
                        }
                    }
                ],
                "x-plnnrly-permission": null,
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:destroy:project_management",
                    "api:destroy:project_drive",
                    "api:destroy:customer_drive",
                    "api:destroy:organization_drive",
                    "api:destroy:user_drive"
                ]
            },
            "x-plnnrly-allow": [
                "GET",
                "DELETE"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/users": {
            "get": {
                "operationId": "api.v2.users.index",
                "summary": "List users",
                "description": "Returns the organisation's people, by name A→Z, the order plnnrly's user list uses. Search by name or email address with `q`, which is the practical way to resolve a colleague to the `user_id` every other collection filters on. Service accounts are never listed. Roles, permissions and teams are not part of this payload and no filter narrows by them. The working week is a detail-view field: send `view=detail` to get `working_hours`. `?include=` is not accepted: this resource expands no relation, and `company_branch` in particular belongs to customers, not to users. Soft-deleted users are excluded by default; `trashed=with` includes them and `trashed=only` returns just the departed, which is how an incremental sync learns somebody left.",
                "tags": [
                    "Organization"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:users"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:users"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/PerPage"
                    },
                    {
                        "$ref": "#/components/parameters/Cursor"
                    },
                    {
                        "$ref": "#/components/parameters/PageNumber"
                    },
                    {
                        "$ref": "#/components/parameters/ViewSummary"
                    },
                    {
                        "$ref": "#/components/parameters/Trashed"
                    },
                    {
                        "name": "filter[birthday]",
                        "in": "query",
                        "description": "Date of birth. `null` selects the users whose birthday was never recorded.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[birthday][gte]",
                        "in": "query",
                        "description": "Date of birth. `null` selects the users whose birthday was never recorded.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[birthday][lte]",
                        "in": "query",
                        "description": "Date of birth. `null` selects the users whose birthday was never recorded.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[birthday][null]",
                        "in": "query",
                        "description": "Date of birth. `null` selects the users whose birthday was never recorded. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[created_at]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][gte]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][lt]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[date_employed]",
                        "in": "query",
                        "description": "First day of employment. `null` selects the users whose start date was never recorded.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[date_employed][gte]",
                        "in": "query",
                        "description": "First day of employment. `null` selects the users whose start date was never recorded.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[date_employed][lte]",
                        "in": "query",
                        "description": "First day of employment. `null` selects the users whose start date was never recorded.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[date_employed][null]",
                        "in": "query",
                        "description": "First day of employment. `null` selects the users whose start date was never recorded. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[updated_at]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][gte]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][lt]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/UserSort"
                    },
                    {
                        "name": "q",
                        "in": "query",
                        "description": "Free-text search over `name`, `email`, in that order of relevance. At most one substring match per request: `q` together with a `contains` filter is 400 invalid-query-parameter.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "minLength": 1
                        }
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "A page of records.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record.",
                                "schema": {
                                    "type": "string",
                                    "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/UserList"
                                            }
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/UsersIndexMeta"
                                        },
                                        "links": {
                                            "$ref": "#/components/schemas/PageLinks"
                                        }
                                    },
                                    "required": [
                                        "data",
                                        "meta",
                                        "links"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Find a colleague by name",
                        "query": {
                            "q": "Jansen"
                        }
                    },
                    {
                        "name": "Who joined this year, with their working week",
                        "query": {
                            "filter[date_employed][gte]": "2026-01-01",
                            "sort": "date_employed",
                            "view": "detail"
                        }
                    },
                    {
                        "name": "The employees who have left",
                        "query": {
                            "trashed": "only"
                        }
                    }
                ],
                "x-plnnrly-permission": "users::read",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1,
                    "view_detail_total": 3,
                    "surcharges": {
                        "q": 2
                    }
                },
                "x-plnnrly-token-ability": [
                    "api:read:users"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/UserList",
                    "detail": "#/components/schemas/User"
                }
            },
            "x-plnnrly-allow": [
                "GET"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/users/{user}": {
            "get": {
                "operationId": "api.v2.users.show",
                "summary": "Read a user",
                "description": "Returns one user. The detail view adds `working_hours`; everything else is in both views. A caller without `users::read` gets `404`, never `403`: a `403` on an id confirms the id exists, so the two answers would enumerate the organisation's people. A service account is `404` for the same reason it is absent from the list. A soft-deleted user is `404` unless you pass `trashed=with`; `trashed=only` returns a departed user and 404s a current one, which makes it an existence probe for a sync client and nothing else. There is no restore action on this resource, and reading a trashed row needs only `users::read`.",
                "tags": [
                    "Organization"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:users"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:users"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "user",
                        "in": "path",
                        "description": "The User's numeric id. Accepts a soft-deleted record.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/Trashed"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/User"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "One user, with their working week",
                        "path": {
                            "user": 1
                        },
                        "query": {
                            "view": "detail"
                        }
                    },
                    {
                        "name": "A user who has left the organisation",
                        "path": {
                            "user": 1
                        },
                        "query": {
                            "trashed": "with"
                        }
                    }
                ],
                "x-plnnrly-permission": "users::read",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:read:users"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/UserList",
                    "detail": "#/components/schemas/User"
                }
            },
            "x-plnnrly-allow": [
                "GET"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/work-item-links/{work_item_link}": {
            "get": {
                "operationId": "api.v2.work-item-links.show",
                "summary": "Read a work item link",
                "description": "Returns one link on its own terms. There is no work item in this path, so `direction` is always `forward` and `relationship` is the forward label; read the link through `work-items/{work_item}/links` to see it from one end's point of view. The summary and detail views are identical on this resource. Links are hard-deleted, and deleting either work item deletes the link with it, so a gone link is simply 404 and `trashed` is not an accepted parameter. Reading additionally requires `project_management::manage_work_items` on the source work item.",
                "tags": [
                    "Work items"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:project_management"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:project_management"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "work_item_link",
                        "in": "path",
                        "description": "The WorkItemLink's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/WorkItemLink"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "One link with both work items expanded",
                        "path": {
                            "work_item_link": 1
                        },
                        "query": {
                            "include": "source_work_item,target_work_item"
                        }
                    }
                ],
                "x-plnnrly-permission": "project_management::view_backlog",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:read:project_management"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/WorkItemLinkList",
                    "detail": "#/components/schemas/WorkItemLink"
                }
            },
            "delete": {
                "operationId": "api.v2.work-item-links.destroy",
                "summary": "Delete a work item link",
                "description": "Permanently unlinks two work items and answers `204`, like the \"remove linked item\" action in plnnrly. There is no soft delete, no ?force parameter and no restore verb, and there is no PATCH either: to change a relationship, DELETE the link and POST a new one to `work-items/{work_item}/links`. Neither work item is otherwise affected. Links are hard-deleted and leave no tombstone, so retrying a delete that already succeeded answers `404` rather than `204`; treat that as success.",
                "tags": [
                    "Work items"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:destroy:project_management"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "work_item_link",
                        "in": "path",
                        "description": "The WorkItemLink's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "responses": {
                    "204": {
                        "description": "Deleted. No body.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Unlink two work items",
                        "path": {
                            "work_item_link": 1
                        }
                    }
                ],
                "x-plnnrly-permission": "project_management::manage_work_items",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:destroy:project_management"
                ]
            },
            "x-plnnrly-allow": [
                "GET",
                "DELETE"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/work-items": {
            "get": {
                "operationId": "api.v2.work-items.index",
                "summary": "List work items",
                "description": "Returns the work items the caller may read, in each sprint's display order with the id as the tiebreaker. The list covers the whole organisation and interleaves projects, because `order` is unique within a sprint only; narrow it with `filter[project_id]`, and add `filter[sprint_id][null]=true` for a project's backlog or `filter[resolved]=false` for its open work. Pagination is cursor-only: a backlog drag rewrites `order` on the rows behind you, so an offset page would skip and repeat items. `meta.page.total` is `null` unless you pass `with_total=1`, which costs two rate units. Listing additionally requires `project_management::manage_work_items`. Defaults to the summary view, which omits `description`; pass `view=detail` for the full payload.",
                "tags": [
                    "Work items"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:project_management"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:project_management"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/PerPage"
                    },
                    {
                        "$ref": "#/components/parameters/Cursor"
                    },
                    {
                        "$ref": "#/components/parameters/ViewSummary"
                    },
                    {
                        "$ref": "#/components/parameters/WithTotal"
                    },
                    {
                        "name": "filter[assignee_id]",
                        "in": "query",
                        "description": "User the work is assigned to. Accepts `me`. Pass `[null]` for unassigned items.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[assignee_id][in]",
                        "in": "query",
                        "description": "User the work is assigned to. Accepts `me`. Pass `[null]` for unassigned items. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[assignee_id][null]",
                        "in": "query",
                        "description": "User the work is assigned to. Accepts `me`. Pass `[null]` for unassigned items. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[created_at]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][gte]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][lt]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[drive_items.id]",
                        "in": "query",
                        "description": "Work items linked to a given drive document. The document must be one the caller may open; a document they cannot open matches nothing.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        },
                        "x-plnnrly-requires": {
                            "permission": "project_drive::access",
                            "abilities": [
                                "api:read:project_drive"
                            ]
                        }
                    },
                    {
                        "name": "filter[due_date]",
                        "in": "query",
                        "description": "Due date, a zone-free calendar date. Combine `[gte]` and `[lte]` for a period; pass `[null]` for items with no due date.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[due_date][gte]",
                        "in": "query",
                        "description": "Due date, a zone-free calendar date. Combine `[gte]` and `[lte]` for a period; pass `[null]` for items with no due date.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[due_date][lte]",
                        "in": "query",
                        "description": "Due date, a zone-free calendar date. Combine `[gte]` and `[lte]` for a period; pass `[null]` for items with no due date.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[due_date][null]",
                        "in": "query",
                        "description": "Due date, a zone-free calendar date. Combine `[gte]` and `[lte]` for a period; pass `[null]` for items with no due date. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[labels.id]",
                        "in": "query",
                        "description": "Work items carrying a given label.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[parent_id]",
                        "in": "query",
                        "description": "Parent work item. Pass a key for one item's children (sub-issues), or `[null]` for top-level items only.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[parent_id][in]",
                        "in": "query",
                        "description": "Parent work item. Pass a key for one item's children (sub-issues), or `[null]` for top-level items only. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[parent_id][null]",
                        "in": "query",
                        "description": "Parent work item. Pass a key for one item's children (sub-issues), or `[null]` for top-level items only. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[priority]",
                        "in": "query",
                        "description": "Relative priority. One of highest, high, normal, low, lowest; there is no medium.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "highest",
                                "high",
                                "normal",
                                "low",
                                "lowest"
                            ],
                            "x-extensible-enum": true
                        }
                    },
                    {
                        "name": "filter[priority][ne]",
                        "in": "query",
                        "description": "Relative priority. One of highest, high, normal, low, lowest; there is no medium.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "highest",
                                "high",
                                "normal",
                                "low",
                                "lowest"
                            ],
                            "x-extensible-enum": true
                        }
                    },
                    {
                        "name": "filter[priority][in]",
                        "in": "query",
                        "description": "Relative priority. One of highest, high, normal, low, lowest; there is no medium. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "string",
                                "enum": [
                                    "highest",
                                    "high",
                                    "normal",
                                    "low",
                                    "lowest"
                                ],
                                "x-extensible-enum": true
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[project_id]",
                        "in": "query",
                        "description": "Project the work item belongs to. This is how a client reproduces one project's backlog; combine it with the other filters to hit `idx_work_items_project_sprint_order` and its siblings.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[project_id][in]",
                        "in": "query",
                        "description": "Project the work item belongs to. This is how a client reproduces one project's backlog; combine it with the other filters to hit `idx_work_items_project_sprint_order` and its siblings. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[reporter_id]",
                        "in": "query",
                        "description": "User who filed the work item. Accepts `me`.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[reporter_id][in]",
                        "in": "query",
                        "description": "User who filed the work item. Accepts `me`. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[resolved]",
                        "in": "query",
                        "description": "Whether the item sits in a workflow status marked as a resolution. Pass `false` for open work.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        },
                        "x-plnnrly-requires": {
                            "permission": "workflows::read",
                            "abilities": [
                                "api:read:workflows"
                            ]
                        }
                    },
                    {
                        "name": "filter[sprint_id]",
                        "in": "query",
                        "description": "Sprint the item sits in. Pass `[null]` for the backlog, the items in no sprint.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[sprint_id][in]",
                        "in": "query",
                        "description": "Sprint the item sits in. Pass `[null]` for the backlog, the items in no sprint. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[sprint_id][null]",
                        "in": "query",
                        "description": "Sprint the item sits in. Pass `[null]` for the backlog, the items in no sprint. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[task_id]",
                        "in": "query",
                        "description": "Kanban task the item is booked against. Pass `[null]` for items booked against no task.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[task_id][in]",
                        "in": "query",
                        "description": "Kanban task the item is booked against. Pass `[null]` for items booked against no task. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[task_id][null]",
                        "in": "query",
                        "description": "Kanban task the item is booked against. Pass `[null]` for items booked against no task. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[updated_at]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][gte]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][lt]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[work_type_id]",
                        "in": "query",
                        "description": "Work type of the item.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[work_type_id][in]",
                        "in": "query",
                        "description": "Work type of the item. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[workflow_status_id]",
                        "in": "query",
                        "description": "Workflow status the item currently sits in.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[workflow_status_id][in]",
                        "in": "query",
                        "description": "Workflow status the item currently sits in. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "$ref": "#/components/parameters/WorkItemSort"
                    },
                    {
                        "name": "q",
                        "in": "query",
                        "description": "Free-text search over `title`, `issue_key`, in that order of relevance. At most one substring match per request: `q` together with a `contains` filter is 400 invalid-query-parameter.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "minLength": 1
                        }
                    },
                    {
                        "$ref": "#/components/parameters/WorkItemInclude"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "A page of records.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record.",
                                "schema": {
                                    "type": "string",
                                    "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/WorkItemList"
                                            }
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/WorkItemsIndexMeta"
                                        },
                                        "links": {
                                            "$ref": "#/components/schemas/PageLinks"
                                        }
                                    },
                                    "required": [
                                        "data",
                                        "meta",
                                        "links"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "One project's open backlog, highest priority first",
                        "query": {
                            "filter": {
                                "project_id": 1,
                                "sprint_id": {
                                    "null": "true"
                                },
                                "resolved": "false"
                            },
                            "sort": "order",
                            "with_total": "1"
                        }
                    }
                ],
                "x-plnnrly-permission": "project_management::view_backlog",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1,
                    "view_detail_total": 3,
                    "surcharges": {
                        "with_total": 2,
                        "include": 1,
                        "include_to_many": 3,
                        "q": 2
                    }
                },
                "x-plnnrly-token-ability": [
                    "api:read:project_management"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/WorkItemList",
                    "detail": "#/components/schemas/WorkItem"
                }
            },
            "post": {
                "operationId": "api.v2.work-items.store",
                "summary": "Create a work item",
                "description": "Creates a work item and returns it in the detail view with a `Location` header. The issue key is generated from the project's customer code and a per-customer sequence; `order` places the item last in its sprint or backlog. `reporter_id` is always the authenticated user: there is no way to file on somebody else's behalf. Omit `workflow_status_id` and `work_type_id` and the server picks both; in an organisation with no work types, or a project whose workflow has no statuses, that is a `409` `state-conflict` with `conflict.reason` unconfigured rather than a validation error about a key you never sent; the fix is an admin action. `label_ids` replaces the label set outright, and `drive_item_ids` the set of linked drive documents; each id must name a document the caller may open, and attaching one additionally requires the `project_drive` prefix's access permission and a token scope covering it. `task_id` must name a task in the project, and an item placed in the running sprint of a sprint project needs one; a simple-mode project does not. A work item cannot be moved between projects afterwards, so pick the project carefully.",
                "tags": [
                    "Work items"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:project_management"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:project_management"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "project_id": {
                                        "type": "integer",
                                        "x-plnnrly-references": {
                                            "resource": "Project",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "projects",
                                        "description": "Project this work item belongs to. It cannot change: the issue key was allocated from the project's customer code."
                                    },
                                    "title": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "description": "Work item title. The other field `?q=` searches."
                                    },
                                    "description": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "description": "Work item description. HTML from the rich editor, sanitised on read and on write. Mention spans are preserved verbatim, so pass them back unchanged. Detail view only."
                                    },
                                    "priority": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "enum": [
                                            "highest",
                                            "high",
                                            "normal",
                                            "low",
                                            "lowest",
                                            null
                                        ],
                                        "x-enum-descriptions": {
                                            "high": "Take this before normal work, but not ahead of the current task.",
                                            "highest": "Drop everything: work on this ahead of anything else.",
                                            "low": "Pick this up once higher-priority work is clear.",
                                            "lowest": "Nice to have, with no expectation it is scheduled at all.",
                                            "normal": "The default: ordinary sprint or backlog work."
                                        },
                                        "x-extensible-enum": true,
                                        "description": "Relative priority. Defaults to normal on create; `null` only on a legacy item that was never prioritised."
                                    },
                                    "due_date": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "format": "date",
                                        "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                                        "description": "Due date, a zone-free calendar date. Null when no date was set."
                                    },
                                    "estimation": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "minimum": 0,
                                        "description": "Original estimate, in minutes."
                                    },
                                    "remaining_estimate": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "minimum": 0,
                                        "description": "Remaining estimate, in minutes. Null where none was entered, in which case plnnrly derives one from the estimate and the logged time."
                                    },
                                    "location_name": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 255,
                                        "x-plnnrly-constraint": "Required when location_latitude, location_longitude is present.",
                                        "description": "Name of the place the work happens, as picked from the address search in plnnrly. Null when no location was set. Send it together with `location_latitude` and `location_longitude`; `null` all three to clear the location. Detail view only."
                                    },
                                    "location_address": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 255,
                                        "description": "Formatted address of the location, when the place has one. Detail view only."
                                    },
                                    "location_latitude": {
                                        "type": [
                                            "number",
                                            "null"
                                        ],
                                        "minimum": -90,
                                        "maximum": 90,
                                        "x-plnnrly-constraint": "Required when location_name, location_longitude is present.",
                                        "description": "Latitude of the location in decimal degrees, -90 to 90. Detail view only."
                                    },
                                    "location_longitude": {
                                        "type": [
                                            "number",
                                            "null"
                                        ],
                                        "minimum": -180,
                                        "maximum": 180,
                                        "x-plnnrly-constraint": "Required when location_name, location_latitude is present.",
                                        "description": "Longitude of the location in decimal degrees, -180 to 180. Detail view only."
                                    },
                                    "work_type_id": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "x-plnnrly-references": {
                                            "resource": "WorkType",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "work_types",
                                        "description": "Work type of this item."
                                    },
                                    "workflow_status_id": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "x-plnnrly-references": {
                                            "resource": "WorkflowStatus",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "workflows",
                                        "description": "Current workflow status. Not writable through PATCH: moving between statuses is `POST work-items/{work_item}/actions/transition`, because the move is validated against the project workflow."
                                    },
                                    "task_id": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "x-plnnrly-references": {
                                            "resource": "Task",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "kanban",
                                        "description": "Kanban task this work item is booked against, when it is booked against one. It must belong to the item's project. A sprint project books the running sprint's hours through it, so an item in that project's running sprint needs one; a simple-mode project does not. Setting it also makes the task's owner the assignee. Follow it to `/api/v2/tasks/{task}`."
                                    },
                                    "assignee_id": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "x-plnnrly-references": {
                                            "resource": "User",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "users",
                                        "description": "User the work is assigned to. Null on an unassigned item. On a sprint project, setting it also moves the item to that user's only open task in the same budget, or in the project when the item has no task yet; with several or no candidates the task is left as it is."
                                    },
                                    "sprint_id": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "x-plnnrly-references": {
                                            "resource": "Sprint",
                                            "field": "id"
                                        },
                                        "description": "Sprint the item sits in. Null means it is in the backlog."
                                    },
                                    "parent_id": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "x-plnnrly-references": {
                                            "resource": "WorkItem",
                                            "field": "id"
                                        },
                                        "description": "Parent work item, for a sub-issue. Null on a top-level item."
                                    },
                                    "label_ids": {
                                        "type": "array",
                                        "items": {
                                            "type": "integer",
                                            "x-plnnrly-references": {
                                                "resource": "Label",
                                                "field": "id"
                                            }
                                        },
                                        "maxItems": 50,
                                        "description": "Labels on this work item. `label_ids` takes at most 50 ids and replaces the whole set: a label you leave out is removed. Build it from `links.labels`, which lists every label while the Sprints feature is enabled, not from `include=labels`, which stops at 25."
                                    },
                                    "drive_item_ids": {
                                        "type": "array",
                                        "items": {
                                            "type": "integer",
                                            "x-plnnrly-references": {
                                                "resource": "DriveItem",
                                                "field": "id"
                                            },
                                            "x-plnnrly-references-prefix": "project_drive"
                                        },
                                        "maxItems": 25,
                                        "description": "Drive documents linked to this work item that you may open. `drive_item_ids` takes at most 25 ids and replaces the whole set: a document you leave out is unlinked, and so is every linked document you cannot open. Build it from `links.drive_items`, not from `include=drive_items`, which stops at 25. Expanding it additionally requires the `project_drive` prefix's access grant and a token scope covering it."
                                    }
                                },
                                "required": [
                                    "project_id",
                                    "title"
                                ],
                                "additionalProperties": false
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Created.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "Idempotency-Replayed": {
                                "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise.",
                                "schema": {
                                    "type": "boolean",
                                    "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise."
                                }
                            },
                            "Location": {
                                "description": "The absolute URL of the created or queued resource.",
                                "schema": {
                                    "type": "string",
                                    "description": "The absolute URL of the created or queued resource."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/WorkItem"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "description": "Conflict.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "oneOf": [
                                        {
                                            "$ref": "#/components/schemas/IdempotencyKeyInFlightProblem"
                                        },
                                        {
                                            "$ref": "#/components/schemas/IdempotencyKeyReusedProblem"
                                        },
                                        {
                                            "$ref": "#/components/schemas/StateConflictProblem"
                                        }
                                    ],
                                    "discriminator": {
                                        "propertyName": "type",
                                        "mapping": {
                                            "https://developers.plnnrly.com/api/v2/problems/idempotency-key-in-flight": "#/components/schemas/IdempotencyKeyInFlightProblem",
                                            "https://developers.plnnrly.com/api/v2/problems/idempotency-key-reused": "#/components/schemas/IdempotencyKeyReusedProblem",
                                            "https://developers.plnnrly.com/api/v2/problems/state-conflict": "#/components/schemas/StateConflictProblem"
                                        }
                                    }
                                },
                                "examples": {
                                    "idempotency-key-in-flight": {
                                        "$ref": "#/components/examples/IdempotencyKeyInFlightProblem"
                                    },
                                    "idempotency-key-reused": {
                                        "$ref": "#/components/examples/IdempotencyKeyReusedProblem"
                                    },
                                    "state-conflict": {
                                        "$ref": "#/components/examples/StateConflictProblem"
                                    }
                                }
                            }
                        }
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "A high-priority item in the backlog",
                        "body": {
                            "project_id": 1,
                            "title": "Portal login rejects SSO users",
                            "priority": "high",
                            "estimation": 240,
                            "label_ids": [
                                1
                            ]
                        }
                    }
                ],
                "x-plnnrly-permission": "project_management::manage_work_items",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:project_management"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/WorkItemList",
                    "detail": "#/components/schemas/WorkItem"
                }
            },
            "x-plnnrly-allow": [
                "GET",
                "POST"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/work-items/{work_item}": {
            "get": {
                "operationId": "api.v2.work-items.show",
                "summary": "Read a work item",
                "description": "Returns one work item in the detail view, addressed by numeric id or by its issue key: `work-items/41` and `work-items/ACM-123` are the same record. The strong `ETag` is derived from `version`, so `If-None-Match` answers `304` on an unchanged item and the same value works as `If-Match` on a write. Reading additionally requires `project_management::manage_work_items`. Comments, subtasks, links, attachments, child work items, labels and linked drive documents are reached through `data.links`; comments, subtasks, labels and drive documents can also be expanded inline with include, up to 25 of each.",
                "tags": [
                    "Work items"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:project_management"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:project_management"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "work_item",
                        "in": "path",
                        "description": "The WorkItem's numeric id. Or its issue_key, matching `[A-Z][A-Z0-9]*-[0-9]+`.",
                        "required": true,
                        "schema": {
                            "oneOf": [
                                {
                                    "type": "integer"
                                },
                                {
                                    "type": "string",
                                    "pattern": "[A-Z][A-Z0-9]*-[0-9]+"
                                }
                            ]
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/Meta"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/WorkItem"
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/WorkItemsShowMeta"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "One work item with its labels and comments",
                        "path": {
                            "work_item": 1
                        },
                        "query": {
                            "include": "labels,comments"
                        }
                    }
                ],
                "x-plnnrly-permission": "project_management::view_backlog",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:read:project_management"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/WorkItemList",
                    "detail": "#/components/schemas/WorkItem"
                }
            },
            "patch": {
                "operationId": "api.v2.work-items.update",
                "summary": "Update a work item",
                "description": "Partially updates a work item: absent keys are untouched, an empty body is rejected. `workflow_status_id` is not accepted here: moving between statuses is validated against the project workflow, so it lives at POST `work-items/{work_item}/actions/transition`. `project_id`, `issue_key`, `order` and `column_order` are not accepted either: a work item cannot change project (its issue key names the project's customer) and its positions change only by dragging in plnnrly. `label_ids` replaces the label set outright and `drive_item_ids` the set of linked drive documents; [] clears either. Each drive item id must name a document the caller may open, which additionally requires the `project_drive` prefix's access permission and a token scope covering it. `task_id` must name a task in the project; sending it makes the task's owner the assignee, and sending `assignee_id` alone on a sprint project moves the item to that user's only open task when there is exactly one. Clearing the task of an item in the running sprint of a sprint project, or moving a task-less item into it, is refused with `422` on `/task_id`; a simple-mode project has no such rule. One PATCH is exactly one write: however many fields the body carries, `version` is bumped once and one update is broadcast to the boards watching this item. A body carrying only `label_ids` or only `drive_item_ids` changes the set without bumping `version`, because those sets are not fields of the work item itself. Send `If-Match` with the `ETag` from your last read to detect a concurrent edit, which answers `412` and reports the current version.",
                "tags": [
                    "Work items"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:project_management"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:project_management"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "work_item",
                        "in": "path",
                        "description": "The WorkItem's numeric id. Or its issue_key, matching `[A-Z][A-Z0-9]*-[0-9]+`.",
                        "required": true,
                        "schema": {
                            "oneOf": [
                                {
                                    "type": "integer"
                                },
                                {
                                    "type": "string",
                                    "pattern": "[A-Z][A-Z0-9]*-[0-9]+"
                                }
                            ]
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "title": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "description": "Work item title. The other field `?q=` searches."
                                    },
                                    "description": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "description": "Work item description. HTML from the rich editor, sanitised on read and on write. Mention spans are preserved verbatim, so pass them back unchanged. Detail view only."
                                    },
                                    "priority": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "enum": [
                                            "highest",
                                            "high",
                                            "normal",
                                            "low",
                                            "lowest",
                                            null
                                        ],
                                        "x-enum-descriptions": {
                                            "high": "Take this before normal work, but not ahead of the current task.",
                                            "highest": "Drop everything: work on this ahead of anything else.",
                                            "low": "Pick this up once higher-priority work is clear.",
                                            "lowest": "Nice to have, with no expectation it is scheduled at all.",
                                            "normal": "The default: ordinary sprint or backlog work."
                                        },
                                        "x-extensible-enum": true,
                                        "description": "Relative priority. Defaults to normal on create; `null` only on a legacy item that was never prioritised."
                                    },
                                    "due_date": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "format": "date",
                                        "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                                        "description": "Due date, a zone-free calendar date. Null when no date was set."
                                    },
                                    "estimation": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "minimum": 0,
                                        "description": "Original estimate, in minutes."
                                    },
                                    "remaining_estimate": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "minimum": 0,
                                        "description": "Remaining estimate, in minutes. Null where none was entered, in which case plnnrly derives one from the estimate and the logged time."
                                    },
                                    "location_name": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 255,
                                        "x-plnnrly-constraint": "Required when location_latitude, location_longitude is present.",
                                        "description": "Name of the place the work happens, as picked from the address search in plnnrly. Null when no location was set. Send it together with `location_latitude` and `location_longitude`; `null` all three to clear the location. Detail view only."
                                    },
                                    "location_address": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 255,
                                        "description": "Formatted address of the location, when the place has one. Detail view only."
                                    },
                                    "location_latitude": {
                                        "type": [
                                            "number",
                                            "null"
                                        ],
                                        "minimum": -90,
                                        "maximum": 90,
                                        "x-plnnrly-constraint": "Required when location_name, location_longitude is present.",
                                        "description": "Latitude of the location in decimal degrees, -90 to 90. Detail view only."
                                    },
                                    "location_longitude": {
                                        "type": [
                                            "number",
                                            "null"
                                        ],
                                        "minimum": -180,
                                        "maximum": 180,
                                        "x-plnnrly-constraint": "Required when location_name, location_latitude is present.",
                                        "description": "Longitude of the location in decimal degrees, -180 to 180. Detail view only."
                                    },
                                    "work_type_id": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "x-plnnrly-references": {
                                            "resource": "WorkType",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "work_types",
                                        "description": "Work type of this item."
                                    },
                                    "task_id": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "x-plnnrly-references": {
                                            "resource": "Task",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "kanban",
                                        "description": "Kanban task this work item is booked against, when it is booked against one. It must belong to the item's project. A sprint project books the running sprint's hours through it, so an item in that project's running sprint needs one; a simple-mode project does not. Setting it also makes the task's owner the assignee. Follow it to `/api/v2/tasks/{task}`."
                                    },
                                    "assignee_id": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "x-plnnrly-references": {
                                            "resource": "User",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "users",
                                        "description": "User the work is assigned to. Null on an unassigned item. On a sprint project, setting it also moves the item to that user's only open task in the same budget, or in the project when the item has no task yet; with several or no candidates the task is left as it is."
                                    },
                                    "sprint_id": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "x-plnnrly-references": {
                                            "resource": "Sprint",
                                            "field": "id"
                                        },
                                        "description": "Sprint the item sits in. Null means it is in the backlog."
                                    },
                                    "parent_id": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "x-plnnrly-references": {
                                            "resource": "WorkItem",
                                            "field": "id"
                                        },
                                        "description": "Parent work item, for a sub-issue. Null on a top-level item."
                                    },
                                    "label_ids": {
                                        "type": "array",
                                        "items": {
                                            "type": "integer",
                                            "x-plnnrly-references": {
                                                "resource": "Label",
                                                "field": "id"
                                            }
                                        },
                                        "maxItems": 50,
                                        "description": "Labels on this work item. `label_ids` takes at most 50 ids and replaces the whole set: a label you leave out is removed. Build it from `links.labels`, which lists every label while the Sprints feature is enabled, not from `include=labels`, which stops at 25."
                                    },
                                    "drive_item_ids": {
                                        "type": "array",
                                        "items": {
                                            "type": "integer",
                                            "x-plnnrly-references": {
                                                "resource": "DriveItem",
                                                "field": "id"
                                            },
                                            "x-plnnrly-references-prefix": "project_drive"
                                        },
                                        "maxItems": 25,
                                        "description": "Drive documents linked to this work item that you may open. `drive_item_ids` takes at most 25 ids and replaces the whole set: a document you leave out is unlinked, and so is every linked document you cannot open. Build it from `links.drive_items`, not from `include=drive_items`, which stops at 25. Expanding it additionally requires the `project_drive` prefix's access grant and a token scope covering it."
                                    }
                                },
                                "additionalProperties": false,
                                "minProperties": 1
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The updated record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/WorkItem"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Reassign and re-estimate",
                        "path": {
                            "work_item": 1
                        },
                        "body": {
                            "assignee_id": 1,
                            "remaining_estimate": 90
                        },
                        "headers": {
                            "If-Match": "*"
                        }
                    }
                ],
                "x-plnnrly-permission": "project_management::manage_work_items",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:project_management"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/WorkItemList",
                    "detail": "#/components/schemas/WorkItem"
                }
            },
            "delete": {
                "operationId": "api.v2.work-items.destroy",
                "summary": "Delete a work item",
                "description": "Permanently deletes a work item. This resource is hard-delete: there is no restore action, no `force` parameter and no `trashed` parameter. Its comments, subtasks, links and attachments go with it. A work item that has child work items is refused with `409` `state-conflict` and `conflict.reason` `has_children`: deleting a parent would silently delete the whole sub-tree. Move or delete the children first. Logged time is untouched: it hangs off the task, not the work item.",
                "tags": [
                    "Work items"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:destroy:project_management"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "work_item",
                        "in": "path",
                        "description": "The WorkItem's numeric id. Or its issue_key, matching `[A-Z][A-Z0-9]*-[0-9]+`.",
                        "required": true,
                        "schema": {
                            "oneOf": [
                                {
                                    "type": "integer"
                                },
                                {
                                    "type": "string",
                                    "pattern": "[A-Z][A-Z0-9]*-[0-9]+"
                                }
                            ]
                        }
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "responses": {
                    "204": {
                        "description": "Deleted. No body.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "description": "Conflict.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/StateConflictProblem"
                                },
                                "examples": {
                                    "state-conflict": {
                                        "$ref": "#/components/examples/StateConflictProblem"
                                    }
                                }
                            }
                        }
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Delete a work item",
                        "path": {
                            "work_item": 1
                        }
                    }
                ],
                "x-plnnrly-permission": "project_management::manage_work_items",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:destroy:project_management"
                ]
            },
            "x-plnnrly-allow": [
                "GET",
                "PATCH",
                "DELETE"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/work-items/{work_item}/actions/move": {
            "post": {
                "operationId": "api.v2.work-items.actions.move",
                "summary": "Move a work item to a position",
                "description": "Places a work item at a position in a list, the way a drag in plnnrly does. `list: board` positions it in the board column of its workflow status and writes `column_order`; `list: backlog` positions it in its sprint, or in the backlog when it is in no sprint, and writes `order`. The same call can change the status (`workflow_status_id`, validated against the project workflow like POST …/actions/transition, so an unreachable status is `409` `state-conflict` with `conflict.reason` `illegal_transition`) and the sprint (`sprint_id`, `null` for the backlog). `after_work_item_id` names the item to land directly below, by id or issue key; `null` puts the item at the top, and leaving it out keeps the item's place in a list it already sits in, or puts it at the end of a new one. The anchor must be in the target list, otherwise 422 at `/after_work_item_id`. A move to where the item already is writes nothing and leaves `version` as it is, so a retry is safe; any other move bumps `version` once. Positions are not consecutive: other items of the list only shift when there is no room, and keep their `version`. Returns the moved work item in the detail view with a fresh `ETag`.",
                "tags": [
                    "Work items"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:project_management"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:project_management"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "work_item",
                        "in": "path",
                        "description": "The WorkItem's numeric id. Or its issue_key, matching `[A-Z][A-Z0-9]*-[0-9]+`.",
                        "required": true,
                        "schema": {
                            "oneOf": [
                                {
                                    "type": "integer"
                                },
                                {
                                    "type": "string",
                                    "pattern": "[A-Z][A-Z0-9]*-[0-9]+"
                                }
                            ]
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "list": {
                                        "type": "string",
                                        "enum": [
                                            "board",
                                            "backlog"
                                        ],
                                        "x-enum-descriptions": {
                                            "backlog": "The sprint the item sits in, or the backlog when it is in no sprint. Writes `order`.",
                                            "board": "The board column of the item's workflow status. Writes `column_order`. On a sprint project the board shows the running sprint only, so the item must be in it."
                                        },
                                        "x-extensible-enum": true,
                                        "description": "Which ordering to write: the board column (`column_order`) or the sprint/backlog list (`order`)."
                                    },
                                    "workflow_status_id": {
                                        "type": "integer",
                                        "x-plnnrly-references": {
                                            "resource": "WorkflowStatus",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "workflows",
                                        "description": "Status to move the item to. Leave it out to keep the current status. It must be reachable from the current status in the project workflow."
                                    },
                                    "sprint_id": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "x-plnnrly-references": {
                                            "resource": "Sprint",
                                            "field": "id"
                                        },
                                        "description": "Sprint to move the item to: a not-yet-completed sprint on the item's own project. Null moves it to the backlog. Leave it out to keep the current sprint."
                                    },
                                    "after_work_item_id": {
                                        "type": [
                                            "integer",
                                            "string",
                                            "null"
                                        ],
                                        "pattern": "^(?:[1-9][0-9]*|[A-Z][A-Z0-9]*-[0-9]+)$",
                                        "description": "Work item to place this one directly below, by id or issue key. It must be in the target list. Null puts the item at the top."
                                    }
                                },
                                "required": [
                                    "list"
                                ],
                                "additionalProperties": false
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The record after the action ran.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "Idempotency-Replayed": {
                                "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise.",
                                "schema": {
                                    "type": "boolean",
                                    "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/WorkItem"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "description": "Conflict.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "oneOf": [
                                        {
                                            "$ref": "#/components/schemas/IdempotencyKeyInFlightProblem"
                                        },
                                        {
                                            "$ref": "#/components/schemas/IdempotencyKeyReusedProblem"
                                        },
                                        {
                                            "$ref": "#/components/schemas/StateConflictProblem"
                                        }
                                    ],
                                    "discriminator": {
                                        "propertyName": "type",
                                        "mapping": {
                                            "https://developers.plnnrly.com/api/v2/problems/idempotency-key-in-flight": "#/components/schemas/IdempotencyKeyInFlightProblem",
                                            "https://developers.plnnrly.com/api/v2/problems/idempotency-key-reused": "#/components/schemas/IdempotencyKeyReusedProblem",
                                            "https://developers.plnnrly.com/api/v2/problems/state-conflict": "#/components/schemas/StateConflictProblem"
                                        }
                                    }
                                },
                                "examples": {
                                    "idempotency-key-in-flight": {
                                        "$ref": "#/components/examples/IdempotencyKeyInFlightProblem"
                                    },
                                    "idempotency-key-reused": {
                                        "$ref": "#/components/examples/IdempotencyKeyReusedProblem"
                                    },
                                    "state-conflict": {
                                        "$ref": "#/components/examples/StateConflictProblem"
                                    }
                                }
                            }
                        }
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Place a card below another in its board column",
                        "path": {
                            "work_item": 1
                        },
                        "body": {
                            "list": "board",
                            "after_work_item_id": 1
                        }
                    },
                    {
                        "name": "Pull an item to the top of a sprint",
                        "path": {
                            "work_item": 1
                        },
                        "body": {
                            "list": "backlog",
                            "sprint_id": 1,
                            "after_work_item_id": null
                        }
                    }
                ],
                "x-plnnrly-permission": "project_management::manage_work_items",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:project_management"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/WorkItemList",
                    "detail": "#/components/schemas/WorkItem"
                }
            },
            "x-plnnrly-allow": [
                "POST"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/work-items/{work_item}/actions/transition": {
            "post": {
                "operationId": "api.v2.work-items.actions.transition",
                "summary": "Transition a work item",
                "description": "Moves a work item to another workflow status. The move is validated against the project workflow: statuses not reachable from the current one are rejected with `409` `state-conflict` and `conflict.reason` `illegal_transition`, naming both ends of the attempted move. For the reachable statuses, send `GET /api/v2/workflow-transitions?filter[from_status_id]={id}` with the item's current `workflow_status_id` as the id; each `to_status_id` is a legal target. Transitioning to the status the item already sits in succeeds and is a no-op, so a retry is safe. Returns the mutated work item in the detail view with a fresh `ETag`, and bumps `version` once.",
                "tags": [
                    "Work items"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:project_management"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:project_management"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "work_item",
                        "in": "path",
                        "description": "The WorkItem's numeric id. Or its issue_key, matching `[A-Z][A-Z0-9]*-[0-9]+`.",
                        "required": true,
                        "schema": {
                            "oneOf": [
                                {
                                    "type": "integer"
                                },
                                {
                                    "type": "string",
                                    "pattern": "[A-Z][A-Z0-9]*-[0-9]+"
                                }
                            ]
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "workflow_status_id": {
                                        "type": "integer",
                                        "x-plnnrly-references": {
                                            "resource": "WorkflowStatus",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "workflows",
                                        "description": "Status to move the work item to. It must be reachable from the current status in the project workflow."
                                    }
                                },
                                "required": [
                                    "workflow_status_id"
                                ],
                                "additionalProperties": false
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The record after the action ran.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "Idempotency-Replayed": {
                                "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise.",
                                "schema": {
                                    "type": "boolean",
                                    "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/WorkItem"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "description": "Conflict.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "oneOf": [
                                        {
                                            "$ref": "#/components/schemas/IdempotencyKeyInFlightProblem"
                                        },
                                        {
                                            "$ref": "#/components/schemas/IdempotencyKeyReusedProblem"
                                        },
                                        {
                                            "$ref": "#/components/schemas/StateConflictProblem"
                                        }
                                    ],
                                    "discriminator": {
                                        "propertyName": "type",
                                        "mapping": {
                                            "https://developers.plnnrly.com/api/v2/problems/idempotency-key-in-flight": "#/components/schemas/IdempotencyKeyInFlightProblem",
                                            "https://developers.plnnrly.com/api/v2/problems/idempotency-key-reused": "#/components/schemas/IdempotencyKeyReusedProblem",
                                            "https://developers.plnnrly.com/api/v2/problems/state-conflict": "#/components/schemas/StateConflictProblem"
                                        }
                                    }
                                },
                                "examples": {
                                    "idempotency-key-in-flight": {
                                        "$ref": "#/components/examples/IdempotencyKeyInFlightProblem"
                                    },
                                    "idempotency-key-reused": {
                                        "$ref": "#/components/examples/IdempotencyKeyReusedProblem"
                                    },
                                    "state-conflict": {
                                        "$ref": "#/components/examples/StateConflictProblem"
                                    }
                                }
                            }
                        }
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Move to In review",
                        "path": {
                            "work_item": 1
                        },
                        "body": {
                            "workflow_status_id": 1
                        }
                    }
                ],
                "x-plnnrly-permission": "project_management::manage_work_items",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:project_management"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/WorkItemList",
                    "detail": "#/components/schemas/WorkItem"
                }
            },
            "x-plnnrly-allow": [
                "POST"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/work-items/{work_item}/attachments": {
            "get": {
                "operationId": "api.v2.work-items.attachments.index",
                "summary": "List a work item's attachments",
                "description": "Returns the files attached to one work item, newest upload first, keyset-paginated. There is no organisation-wide collection of files (a file is only ever reached inside its parent's collection), so this nested path is the only way to list a work item's attachments, and `filter[model_type]` / `filter[model_id]` do not exist: the parent is the path segment. The path takes the work item's numeric id or its issue key, so `work-items/ACM-123/attachments` and `work-items/41/attachments` are the same list. Work items are hard-deleted, so `trashed` is not an accepted parameter here. Listing additionally requires `project_management::manage_work_items` on the parent work item. The API never streams bytes: pass `include=url` for a signed download URL per file, and attach a new file with the two-step upload flow that starts at `POST work-items/{work_item}/uploads`.",
                "tags": [
                    "Work items"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:project_management"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:project_management"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "work_item",
                        "in": "path",
                        "description": "The WorkItem's numeric id. Or its issue_key, matching `[A-Z][A-Z0-9]*-[0-9]+`.",
                        "required": true,
                        "schema": {
                            "oneOf": [
                                {
                                    "type": "integer"
                                },
                                {
                                    "type": "string",
                                    "pattern": "[A-Z][A-Z0-9]*-[0-9]+"
                                }
                            ]
                        }
                    },
                    {
                        "$ref": "#/components/parameters/PerPage"
                    },
                    {
                        "$ref": "#/components/parameters/Cursor"
                    },
                    {
                        "$ref": "#/components/parameters/PageNumber"
                    },
                    {
                        "$ref": "#/components/parameters/ViewSummary"
                    },
                    {
                        "name": "filter[collection]",
                        "in": "query",
                        "description": "Narrow to one media collection. A work item registers only `attachments`; a drive item registers `attachments`, `content` (inline editor images) and `file` (the single uploaded payload), so `filter[collection]=file` is how a client asks a drive item for its payload alone.",
                        "required": false,
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "filter[collection][starts]",
                        "in": "query",
                        "description": "Narrow to one media collection. A work item registers only `attachments`; a drive item registers `attachments`, `content` (inline editor images) and `file` (the single uploaded payload), so `filter[collection]=file` is how a client asks a drive item for its payload alone. One value, matched literally as a prefix: send commas and backslashes as they are, and `%` and `_` match themselves rather than acting as wildcards.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "minLength": 1
                        }
                    },
                    {
                        "name": "filter[created_at]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][gte]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][lt]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][gte]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][lt]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/MediaSort"
                    },
                    {
                        "name": "q",
                        "in": "query",
                        "description": "Free-text search over `name`. At most one substring match per request: `q` together with a `contains` filter is 400 invalid-query-parameter.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "minLength": 1
                        }
                    },
                    {
                        "$ref": "#/components/parameters/MediaInclude"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "A page of records.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record.",
                                "schema": {
                                    "type": "string",
                                    "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/MediaList"
                                            }
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/WorkItemsAttachmentsIndexMeta"
                                        },
                                        "links": {
                                            "$ref": "#/components/schemas/PageLinks"
                                        }
                                    },
                                    "required": [
                                        "data",
                                        "meta",
                                        "links"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "One work item's attachments with download URLs",
                        "path": {
                            "work_item": 1
                        },
                        "query": {
                            "include": "url,url_expires_at",
                            "per_page": 25
                        }
                    }
                ],
                "x-plnnrly-permission": "project_management::view_backlog",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1,
                    "view_detail_total": 3,
                    "surcharges": {
                        "include": 1,
                        "q": 2
                    }
                },
                "x-plnnrly-token-ability": [
                    "api:read:project_management"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/MediaList",
                    "detail": "#/components/schemas/Media"
                }
            },
            "post": {
                "operationId": "api.v2.work-items.attachments.store",
                "summary": "Attach an uploaded file to a work item",
                "description": "Step 3 of 3. Turns bytes you have already uploaded into an attachment on the work item named in the path, and returns the file in the detail view. `upload_id` is the only key this accepts: create a ticket with `POST work-items/{work_item}/uploads`, PUT the raw bytes to its `upload_url`, then send its id here. Everything about the file (its name, its size, its media type, its collection and its storage location) is read off the ticket and off the bytes that actually arrived, so `file_name`, `mime_type`, `byte_size`, collection and disk are all a `422`. The ticket must have been created for this work item, by this API token, and must not have expired or been registered already; each of those is a specific `409` or `422` naming the cause. The server re-hashes the stored bytes and compares them with what it received before creating anything. This works exactly like an upload in plnnrly, so thumbnails and notifications follow identically. Attaching requires `project_management::manage_work_items`, the same permission plnnrly checks. `Idempotency-Key` is required: a lost 201 retried without one would report a failure for a create that succeeded. There is no `Location` header (a file has no item URL of its own), and no PATCH or DELETE on a file; remove one in plnnrly.",
                "tags": [
                    "Work items"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:project_management"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:project_management"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "work_item",
                        "in": "path",
                        "description": "The WorkItem's numeric id. Or its issue_key, matching `[A-Z][A-Z0-9]*-[0-9]+`.",
                        "required": true,
                        "schema": {
                            "oneOf": [
                                {
                                    "type": "integer"
                                },
                                {
                                    "type": "string",
                                    "pattern": "[A-Z][A-Z0-9]*-[0-9]+"
                                }
                            ]
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKeyRequired"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "upload_id": {
                                        "type": "integer",
                                        "x-plnnrly-references": {
                                            "resource": "Upload",
                                            "field": "id"
                                        },
                                        "description": "The id of the upload ticket whose bytes become this file. Create one with `POST work-items/{work_item}/uploads` or `POST drive-items/{drive_item}/uploads`, PUT the bytes to its `upload_url`, then send its id here. Write-only: it is never rendered on a media row, and it is the only key either attachment store accepts.",
                                        "writeOnly": true
                                    }
                                },
                                "required": [
                                    "upload_id"
                                ],
                                "additionalProperties": false
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Created.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "Idempotency-Replayed": {
                                "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise.",
                                "schema": {
                                    "type": "boolean",
                                    "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Media"
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/WorkItemsAttachmentsStoreMeta"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "description": "Conflict.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "oneOf": [
                                        {
                                            "$ref": "#/components/schemas/IdempotencyKeyInFlightProblem"
                                        },
                                        {
                                            "$ref": "#/components/schemas/IdempotencyKeyReusedProblem"
                                        },
                                        {
                                            "$ref": "#/components/schemas/StateConflictProblem"
                                        }
                                    ],
                                    "discriminator": {
                                        "propertyName": "type",
                                        "mapping": {
                                            "https://developers.plnnrly.com/api/v2/problems/idempotency-key-in-flight": "#/components/schemas/IdempotencyKeyInFlightProblem",
                                            "https://developers.plnnrly.com/api/v2/problems/idempotency-key-reused": "#/components/schemas/IdempotencyKeyReusedProblem",
                                            "https://developers.plnnrly.com/api/v2/problems/state-conflict": "#/components/schemas/StateConflictProblem"
                                        }
                                    }
                                },
                                "examples": {
                                    "idempotency-key-in-flight": {
                                        "$ref": "#/components/examples/IdempotencyKeyInFlightProblem"
                                    },
                                    "idempotency-key-reused": {
                                        "$ref": "#/components/examples/IdempotencyKeyReusedProblem"
                                    },
                                    "state-conflict": {
                                        "$ref": "#/components/examples/StateConflictProblem"
                                    }
                                }
                            }
                        }
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Register staged bytes as an attachment",
                        "path": {
                            "work_item": 1
                        },
                        "body": {
                            "upload_id": 1
                        }
                    }
                ],
                "x-plnnrly-permission": "project_management::manage_work_items",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:project_management"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/MediaList",
                    "detail": "#/components/schemas/Media"
                }
            },
            "x-plnnrly-allow": [
                "GET",
                "POST"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/work-items/{work_item}/comments": {
            "get": {
                "operationId": "api.v2.work-items.comments.index",
                "summary": "List a work item's comments",
                "description": "Returns the comment thread on one work item, newest first, keyset-paginated. There is no organisation-wide collection of comments, so this nested path is the only way to list them and `filter[commentable_type]` / `filter[commentable_id]` do not exist: the parent is the path segment. The path takes the work item's numeric id or its issue key, so `work-items/ACM-123/comments` and `work-items/41/comments` are the same thread. Work items are hard-deleted, so `trashed` is not an accepted parameter here. Listing additionally requires `project_management::manage_work_items` on the parent work item. Pass `include=author` to expand each comment's author; the body is in both views.",
                "tags": [
                    "Work items"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:project_management"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:project_management"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "work_item",
                        "in": "path",
                        "description": "The WorkItem's numeric id. Or its issue_key, matching `[A-Z][A-Z0-9]*-[0-9]+`.",
                        "required": true,
                        "schema": {
                            "oneOf": [
                                {
                                    "type": "integer"
                                },
                                {
                                    "type": "string",
                                    "pattern": "[A-Z][A-Z0-9]*-[0-9]+"
                                }
                            ]
                        }
                    },
                    {
                        "$ref": "#/components/parameters/PerPage"
                    },
                    {
                        "$ref": "#/components/parameters/Cursor"
                    },
                    {
                        "$ref": "#/components/parameters/ViewSummary"
                    },
                    {
                        "$ref": "#/components/parameters/WithTotal"
                    },
                    {
                        "name": "filter[author_id]",
                        "in": "query",
                        "description": "Id of the comment's author. Send together with `author_type`.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        },
                        "x-plnnrly-constraint": "Send author_id and author_type together."
                    },
                    {
                        "name": "filter[author_type]",
                        "in": "query",
                        "description": "Type of the comment's author. `user` is the only value in v2.0. Send together with `author_id`.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "x-extensible-enum": true
                        },
                        "x-plnnrly-constraint": "Send author_type and author_id together."
                    },
                    {
                        "name": "filter[created_at]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][gte]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][lt]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][gte]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][lt]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/CommentSort"
                    },
                    {
                        "$ref": "#/components/parameters/CommentInclude"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "A page of records.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record.",
                                "schema": {
                                    "type": "string",
                                    "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/CommentList"
                                            }
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/WorkItemsCommentsIndexMeta"
                                        },
                                        "links": {
                                            "$ref": "#/components/schemas/PageLinks"
                                        }
                                    },
                                    "required": [
                                        "data",
                                        "meta",
                                        "links"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "One work item's thread with its authors",
                        "path": {
                            "work_item": 1
                        },
                        "query": {
                            "include": "author",
                            "per_page": 25
                        }
                    }
                ],
                "x-plnnrly-permission": "project_management::view_backlog",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1,
                    "view_detail_total": 3,
                    "surcharges": {
                        "with_total": 2,
                        "include": 1
                    }
                },
                "x-plnnrly-token-ability": [
                    "api:read:project_management"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/CommentList",
                    "detail": "#/components/schemas/Comment"
                }
            },
            "post": {
                "operationId": "api.v2.work-items.comments.store",
                "summary": "Post a comment on a work item",
                "description": "Creates one comment on the work item named in the path, authored as the authenticated user, and returns it in the detail view with a `Location` header pointing at the flat item path. `body` is the only accepted key: the work item comes from the path, so `commentable_type` and `commentable_id` in the body are a `422`, and the author is the caller, so `author_type` and `author_id` are never accepted. The body is rich text: mention spans read from an existing comment may be posted back verbatim and are preserved, but constructing a new mention is not supported in v2.0. Posting works exactly like posting in plnnrly, so mention notifications, thread events and the author's auto-subscription all fire. A deleted work item is `404`.",
                "tags": [
                    "Work items"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:project_management"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:project_management"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "work_item",
                        "in": "path",
                        "description": "The WorkItem's numeric id. Or its issue_key, matching `[A-Z][A-Z0-9]*-[0-9]+`.",
                        "required": true,
                        "schema": {
                            "oneOf": [
                                {
                                    "type": "integer"
                                },
                                {
                                    "type": "string",
                                    "pattern": "[A-Z][A-Z0-9]*-[0-9]+"
                                }
                            ]
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "body": {
                                        "type": "string",
                                        "maxLength": 65535,
                                        "description": "Comment body as rich text. HTML from the plnnrly editor, sanitised on the way out, so what you read back can differ from the bytes you sent. Mention spans (`<span data-type=\"mention\" data-id=\"3\">`) survive untouched and may be posted back verbatim; constructing a new mention is not supported in v2.0."
                                    }
                                },
                                "required": [
                                    "body"
                                ],
                                "additionalProperties": false
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Created.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "Idempotency-Replayed": {
                                "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise.",
                                "schema": {
                                    "type": "boolean",
                                    "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise."
                                }
                            },
                            "Location": {
                                "description": "The absolute URL of the created or queued resource.",
                                "schema": {
                                    "type": "string",
                                    "description": "The absolute URL of the created or queued resource."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Comment"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "$ref": "#/components/responses/Problem409Idempotency"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "A comment on a work item",
                        "path": {
                            "work_item": 1
                        },
                        "body": {
                            "body": "<p>Deployed to staging, ready for review.</p>"
                        }
                    }
                ],
                "x-plnnrly-permission": "project_management::manage_work_items",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:project_management"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/CommentList",
                    "detail": "#/components/schemas/Comment"
                }
            },
            "x-plnnrly-allow": [
                "GET",
                "POST"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/work-items/{work_item}/links": {
            "get": {
                "operationId": "api.v2.work-items.links.index",
                "summary": "List a work item's links",
                "description": "Returns every link touching one work item, in both directions: the links it is the source of and the links it is the target of, which is what the linked items section in plnnrly shows. `direction` and `relationship` on each row are relative to the work item in the path, so the same link reads `forward` / `blocks` here and `inverse` / `is blocked by` from the other end. The path accepts a numeric id or an issue key, so `work-items/ACM-123/links` works. There is no organisation-wide collection of links and no `filter[source_work_item_id]` or `filter[target_work_item_id]`: the path supplies both. Ordered by id ascending, and `meta.page.total` is always present; `?with_total` is a `400`. Listing additionally requires `project_management::manage_work_items` on the work item.",
                "tags": [
                    "Work items"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:project_management"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:project_management"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "work_item",
                        "in": "path",
                        "description": "The WorkItem's numeric id. Or its issue_key, matching `[A-Z][A-Z0-9]*-[0-9]+`.",
                        "required": true,
                        "schema": {
                            "oneOf": [
                                {
                                    "type": "integer"
                                },
                                {
                                    "type": "string",
                                    "pattern": "[A-Z][A-Z0-9]*-[0-9]+"
                                }
                            ]
                        }
                    },
                    {
                        "$ref": "#/components/parameters/PerPage"
                    },
                    {
                        "$ref": "#/components/parameters/Cursor"
                    },
                    {
                        "$ref": "#/components/parameters/PageNumber"
                    },
                    {
                        "$ref": "#/components/parameters/ViewSummary"
                    },
                    {
                        "name": "filter[created_at]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][gte]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][lt]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_by]",
                        "in": "query",
                        "description": "User who created the link. `[null]` selects links whose creator has since been deleted.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[created_by][in]",
                        "in": "query",
                        "description": "User who created the link. `[null]` selects links whose creator has since been deleted. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[created_by][null]",
                        "in": "query",
                        "description": "User who created the link. `[null]` selects links whose creator has since been deleted. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[link_type]",
                        "in": "query",
                        "description": "Relationship type: `blocks`, `clones`, `duplicates`, `merged_into`, `causes`, `relates_to` or `split_to`. The value is stored from the source item's point of view, so it does not flip on an inverse link: filter on `blocks` and you get both the items this one blocks and the items that block it.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "blocks",
                                "clones",
                                "duplicates",
                                "merged_into",
                                "causes",
                                "relates_to",
                                "split_to"
                            ],
                            "x-extensible-enum": true
                        }
                    },
                    {
                        "name": "filter[link_type][ne]",
                        "in": "query",
                        "description": "Relationship type: `blocks`, `clones`, `duplicates`, `merged_into`, `causes`, `relates_to` or `split_to`. The value is stored from the source item's point of view, so it does not flip on an inverse link: filter on `blocks` and you get both the items this one blocks and the items that block it.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "blocks",
                                "clones",
                                "duplicates",
                                "merged_into",
                                "causes",
                                "relates_to",
                                "split_to"
                            ],
                            "x-extensible-enum": true
                        }
                    },
                    {
                        "name": "filter[link_type][in]",
                        "in": "query",
                        "description": "Relationship type: `blocks`, `clones`, `duplicates`, `merged_into`, `causes`, `relates_to` or `split_to`. The value is stored from the source item's point of view, so it does not flip on an inverse link: filter on `blocks` and you get both the items this one blocks and the items that block it. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "string",
                                "enum": [
                                    "blocks",
                                    "clones",
                                    "duplicates",
                                    "merged_into",
                                    "causes",
                                    "relates_to",
                                    "split_to"
                                ],
                                "x-extensible-enum": true
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[updated_at]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][gte]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][lt]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/WorkItemLinkSort"
                    },
                    {
                        "$ref": "#/components/parameters/WorkItemLinkInclude"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "A page of records.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record.",
                                "schema": {
                                    "type": "string",
                                    "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/WorkItemLinkList"
                                            }
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/WorkItemsLinksIndexMeta"
                                        },
                                        "links": {
                                            "$ref": "#/components/schemas/PageLinks"
                                        }
                                    },
                                    "required": [
                                        "data",
                                        "meta",
                                        "links"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Everything blocking, or blocked by, one work item",
                        "path": {
                            "work_item": 1
                        },
                        "query": {
                            "filter": {
                                "link_type": "blocks"
                            },
                            "include": "source_work_item,target_work_item"
                        }
                    }
                ],
                "x-plnnrly-permission": "project_management::view_backlog",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1,
                    "view_detail_total": 3,
                    "surcharges": {
                        "include": 1
                    }
                },
                "x-plnnrly-token-ability": [
                    "api:read:project_management"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/WorkItemLinkList",
                    "detail": "#/components/schemas/WorkItemLink"
                }
            },
            "post": {
                "operationId": "api.v2.work-items.links.store",
                "summary": "Link a work item to another",
                "description": "Creates a link from the work item in the path to another work item in the same project, and returns it with a `Location` header pointing at the flat item path. The body is exactly `link_type` and `target_work_item_id`: the source comes from the path, so `source_work_item_id` is not accepted, and `created_by` is the authenticated caller. To create the inverse of a relationship (\"this item is blocked by ACM-9\"), post to ACM-9's own collection instead: `POST work-items/ACM-9/links` with `link_type=blocks` and this item as the target stores the identical row, which both ends then read from their own side. Three refusals mirror plnnrly: a work item in another project is refused exactly as a nonexistent one is, linking an item to itself is `422` `unprocessable-relationship`, and repeating an existing (source, target, `link_type`) triple is `409` `duplicate-resource`.",
                "tags": [
                    "Work items"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:project_management"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:project_management"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "work_item",
                        "in": "path",
                        "description": "The WorkItem's numeric id. Or its issue_key, matching `[A-Z][A-Z0-9]*-[0-9]+`.",
                        "required": true,
                        "schema": {
                            "oneOf": [
                                {
                                    "type": "integer"
                                },
                                {
                                    "type": "string",
                                    "pattern": "[A-Z][A-Z0-9]*-[0-9]+"
                                }
                            ]
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "link_type": {
                                        "type": "string",
                                        "enum": [
                                            "blocks",
                                            "clones",
                                            "duplicates",
                                            "merged_into",
                                            "causes",
                                            "relates_to",
                                            "split_to"
                                        ],
                                        "x-enum-descriptions": {
                                            "blocks": "The source cannot be finished until the target is, and the target is blocked by the source.",
                                            "causes": "The source is the cause of the target, typically a change that produced a defect.",
                                            "clones": "The source was created as a copy of the target.",
                                            "duplicates": "The source and the target describe the same work; one of the two should be closed.",
                                            "merged_into": "The source was folded into the target, which now carries the work.",
                                            "relates_to": "The two are related with no direction implied. This is the one symmetric type: it reads the same from either side.",
                                            "split_to": "The source was split and the target is one of the pieces it was split into."
                                        },
                                        "x-extensible-enum": true,
                                        "description": "The relationship, stored from the source item's point of view: `blocks`, `clones`, `duplicates`, `merged_into`, `causes`, `relates_to` or `split_to`. Only `relates_to` is symmetric."
                                    },
                                    "target_work_item_id": {
                                        "type": "integer",
                                        "x-plnnrly-references": {
                                            "resource": "WorkItem",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "project_management",
                                        "description": "Work item the link points at. Always in the same project as the source."
                                    }
                                },
                                "required": [
                                    "link_type",
                                    "target_work_item_id"
                                ],
                                "additionalProperties": false
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Created.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "Idempotency-Replayed": {
                                "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise.",
                                "schema": {
                                    "type": "boolean",
                                    "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise."
                                }
                            },
                            "Location": {
                                "description": "The absolute URL of the created or queued resource.",
                                "schema": {
                                    "type": "string",
                                    "description": "The absolute URL of the created or queued resource."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/WorkItemLink"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "description": "Conflict.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "oneOf": [
                                        {
                                            "$ref": "#/components/schemas/DuplicateResourceProblem"
                                        },
                                        {
                                            "$ref": "#/components/schemas/IdempotencyKeyInFlightProblem"
                                        },
                                        {
                                            "$ref": "#/components/schemas/IdempotencyKeyReusedProblem"
                                        }
                                    ],
                                    "discriminator": {
                                        "propertyName": "type",
                                        "mapping": {
                                            "https://developers.plnnrly.com/api/v2/problems/duplicate-resource": "#/components/schemas/DuplicateResourceProblem",
                                            "https://developers.plnnrly.com/api/v2/problems/idempotency-key-in-flight": "#/components/schemas/IdempotencyKeyInFlightProblem",
                                            "https://developers.plnnrly.com/api/v2/problems/idempotency-key-reused": "#/components/schemas/IdempotencyKeyReusedProblem"
                                        }
                                    }
                                },
                                "examples": {
                                    "duplicate-resource": {
                                        "$ref": "#/components/examples/DuplicateResourceProblem"
                                    },
                                    "idempotency-key-in-flight": {
                                        "$ref": "#/components/examples/IdempotencyKeyInFlightProblem"
                                    },
                                    "idempotency-key-reused": {
                                        "$ref": "#/components/examples/IdempotencyKeyReusedProblem"
                                    }
                                }
                            }
                        }
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "This work item blocks another",
                        "path": {
                            "work_item": 1
                        },
                        "body": {
                            "link_type": "blocks",
                            "target_work_item_id": 1
                        }
                    }
                ],
                "x-plnnrly-permission": "project_management::manage_work_items",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:project_management"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/WorkItemLinkList",
                    "detail": "#/components/schemas/WorkItemLink"
                }
            },
            "x-plnnrly-allow": [
                "GET",
                "POST"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/work-items/{work_item}/subtasks": {
            "get": {
                "operationId": "api.v2.work-items.subtasks.index",
                "summary": "List a work item's subtasks",
                "description": "Returns one work item's checklist subtasks in their own checklist order, `order` ascending with the id as the tiebreaker. There is no organisation-wide collection of subtasks: a checklist row only means anything under the work item it belongs to, so this nested path is the only way to list them and `filter[work_item_id]` does not exist. Listing additionally requires `project_management::manage_work_items`. Defaults to the summary view, which omits the rich-text `description`; pass `view=detail` for it, or `include=work_item` to expand the work item each row points at.",
                "tags": [
                    "Work items"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:project_management"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:project_management"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "work_item",
                        "in": "path",
                        "description": "The WorkItem's numeric id. Or its issue_key, matching `[A-Z][A-Z0-9]*-[0-9]+`.",
                        "required": true,
                        "schema": {
                            "oneOf": [
                                {
                                    "type": "integer"
                                },
                                {
                                    "type": "string",
                                    "pattern": "[A-Z][A-Z0-9]*-[0-9]+"
                                }
                            ]
                        }
                    },
                    {
                        "$ref": "#/components/parameters/PerPage"
                    },
                    {
                        "$ref": "#/components/parameters/Cursor"
                    },
                    {
                        "$ref": "#/components/parameters/SubtaskPageNumber"
                    },
                    {
                        "$ref": "#/components/parameters/ViewSummary"
                    },
                    {
                        "name": "filter[created_at]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][gte]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][lt]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[is_resolved]",
                        "in": "query",
                        "description": "Narrow the checklist to the ticked-off or the outstanding subtasks.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[updated_at]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][gte]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][lt]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/SubtaskSort"
                    },
                    {
                        "name": "q",
                        "in": "query",
                        "description": "Free-text search over `title`. At most one substring match per request: `q` together with a `contains` filter is 400 invalid-query-parameter.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "minLength": 1
                        }
                    },
                    {
                        "$ref": "#/components/parameters/SubtaskInclude"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "A page of records.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record.",
                                "schema": {
                                    "type": "string",
                                    "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/SubtaskList"
                                            }
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/WorkItemsSubtasksIndexMeta"
                                        },
                                        "links": {
                                            "$ref": "#/components/schemas/PageLinks"
                                        }
                                    },
                                    "required": [
                                        "data",
                                        "meta",
                                        "links"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "One work item's outstanding subtasks",
                        "path": {
                            "work_item": 1
                        },
                        "query": {
                            "filter": {
                                "is_resolved": "false"
                            }
                        }
                    }
                ],
                "x-plnnrly-permission": "project_management::view_backlog",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1,
                    "view_detail_total": 3,
                    "surcharges": {
                        "include": 1,
                        "q": 2
                    }
                },
                "x-plnnrly-token-ability": [
                    "api:read:project_management"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/SubtaskList",
                    "detail": "#/components/schemas/Subtask"
                }
            },
            "post": {
                "operationId": "api.v2.work-items.subtasks.store",
                "summary": "Add a subtask to a work item",
                "description": "Creates one checklist subtask on the work item named in the path and returns it in the detail view, with a `Location` header pointing at the flat item path. The work item comes from the path, so `work_item_id` is not accepted in the body and a subtask can never be moved between work items. The new subtask is appended to the end of that work item's checklist; `order` is not accepted, and reordering is not available through the API. There is no batch create: post one subtask per request.",
                "tags": [
                    "Work items"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:project_management"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:project_management"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "work_item",
                        "in": "path",
                        "description": "The WorkItem's numeric id. Or its issue_key, matching `[A-Z][A-Z0-9]*-[0-9]+`.",
                        "required": true,
                        "schema": {
                            "oneOf": [
                                {
                                    "type": "integer"
                                },
                                {
                                    "type": "string",
                                    "pattern": "[A-Z][A-Z0-9]*-[0-9]+"
                                }
                            ]
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "title": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "description": "Subtask title, as shown on the checklist row. This is the field `?q=` searches."
                                    },
                                    "description": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "description": "Optional note on the subtask. HTML from the rich editor, sanitised on read and on write. Null when the subtask is just a title."
                                    },
                                    "is_resolved": {
                                        "type": "boolean",
                                        "description": "Whether the subtask is ticked off."
                                    }
                                },
                                "required": [
                                    "title"
                                ],
                                "additionalProperties": false
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Created.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "Idempotency-Replayed": {
                                "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise.",
                                "schema": {
                                    "type": "boolean",
                                    "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise."
                                }
                            },
                            "Location": {
                                "description": "The absolute URL of the created or queued resource.",
                                "schema": {
                                    "type": "string",
                                    "description": "The absolute URL of the created or queued resource."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Subtask"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "$ref": "#/components/responses/Problem409Idempotency"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "A checklist row on a work item",
                        "path": {
                            "work_item": 1
                        },
                        "body": {
                            "title": "Wire up the SSO callback"
                        }
                    }
                ],
                "x-plnnrly-permission": "project_management::manage_work_items",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:project_management"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/SubtaskList",
                    "detail": "#/components/schemas/Subtask"
                }
            },
            "x-plnnrly-allow": [
                "GET",
                "POST"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/work-items/{work_item}/uploads": {
            "post": {
                "operationId": "api.v2.work-items.uploads.store",
                "summary": "Start a file upload on a work item",
                "description": "Step 1 of 3. Returns an upload ticket whose `upload_url` you PUT the raw bytes to, after which you POST the ticket's id as `upload_id` to `work-items/{work_item}/attachments` to turn those bytes into an attachment. The bytes never travel through this API: `/api/v2` accepts `application/json` and nothing else, so the upload target is a separate signed route. You declare only a file name and a size; the collection, the storage location, the disk and the ticket lifetime are all chosen by the server and no request field can influence them. The declared size is not a bound (the server counts what actually arrives and refuses anything over 10 MB), and an optional `checksum_sha256` extends the integrity guarantee back to your own disk. A file name carrying an executable extension anywhere in it, `shell.php.jpg` included, is refused here rather than after the upload. The ticket is yours alone, is single-use, expires in 30 minutes, and can be deleted with DELETE `uploads/{upload}` while unregistered. You may hold ten outstanding tickets at once. Deleted work items are `404`.",
                "tags": [
                    "Work items"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:project_management"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:project_management"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "work_item",
                        "in": "path",
                        "description": "The WorkItem's numeric id. Or its issue_key, matching `[A-Z][A-Z0-9]*-[0-9]+`.",
                        "required": true,
                        "schema": {
                            "oneOf": [
                                {
                                    "type": "integer"
                                },
                                {
                                    "type": "string",
                                    "pattern": "[A-Z][A-Z0-9]*-[0-9]+"
                                }
                            ]
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "file_name": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "description": "The sanitized file name the file will carry. Sent when the ticket is created; control characters are stripped and `#`, `/`, `\\` and spaces become hyphens, so what you read back can differ from what you sent."
                                    },
                                    "byte_size": {
                                        "type": "integer",
                                        "minimum": 1,
                                        "maximum": 10485760,
                                        "description": "The size you declared when creating the ticket. A declaration, not a bound: the server counts the bytes it actually receives."
                                    },
                                    "checksum_sha256": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "pattern": "^[0-9a-f]{64}$",
                                        "minLength": 64,
                                        "maxLength": 64,
                                        "description": "The optional SHA-256 you declared when creating the ticket. When present the server refuses bytes that do not match it."
                                    }
                                },
                                "required": [
                                    "file_name",
                                    "byte_size"
                                ],
                                "additionalProperties": false
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Created.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "Idempotency-Replayed": {
                                "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise.",
                                "schema": {
                                    "type": "boolean",
                                    "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise."
                                }
                            },
                            "Location": {
                                "description": "The absolute URL of the created or queued resource.",
                                "schema": {
                                    "type": "string",
                                    "description": "The absolute URL of the created or queued resource."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Upload"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "description": "Conflict.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "oneOf": [
                                        {
                                            "$ref": "#/components/schemas/IdempotencyKeyInFlightProblem"
                                        },
                                        {
                                            "$ref": "#/components/schemas/IdempotencyKeyReusedProblem"
                                        },
                                        {
                                            "$ref": "#/components/schemas/StateConflictProblem"
                                        }
                                    ],
                                    "discriminator": {
                                        "propertyName": "type",
                                        "mapping": {
                                            "https://developers.plnnrly.com/api/v2/problems/idempotency-key-in-flight": "#/components/schemas/IdempotencyKeyInFlightProblem",
                                            "https://developers.plnnrly.com/api/v2/problems/idempotency-key-reused": "#/components/schemas/IdempotencyKeyReusedProblem",
                                            "https://developers.plnnrly.com/api/v2/problems/state-conflict": "#/components/schemas/StateConflictProblem"
                                        }
                                    }
                                },
                                "examples": {
                                    "idempotency-key-in-flight": {
                                        "$ref": "#/components/examples/IdempotencyKeyInFlightProblem"
                                    },
                                    "idempotency-key-reused": {
                                        "$ref": "#/components/examples/IdempotencyKeyReusedProblem"
                                    },
                                    "state-conflict": {
                                        "$ref": "#/components/examples/StateConflictProblem"
                                    }
                                }
                            }
                        }
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Stage a report for a work item",
                        "path": {
                            "work_item": 1
                        },
                        "body": {
                            "file_name": "quarterly-report.pdf",
                            "byte_size": 184320
                        }
                    }
                ],
                "x-plnnrly-permission": "project_management::manage_work_items",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:project_management"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/UploadList",
                    "detail": "#/components/schemas/Upload"
                }
            },
            "x-plnnrly-allow": [
                "POST"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/work-types": {
            "get": {
                "operationId": "api.v2.work-types.index",
                "summary": "List work types",
                "description": "Returns the organisation's work-item types (the categories work items are filed under) in plnnrly's display order, with the id as the tiebreaker. Types created through this API are created at position 0, so they tie with each other and sort by id. Available only while the Sprints feature is enabled.",
                "tags": [
                    "Configuration"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:work_types"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:work_types"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/PerPage"
                    },
                    {
                        "$ref": "#/components/parameters/Cursor"
                    },
                    {
                        "$ref": "#/components/parameters/WorkTypePageNumber"
                    },
                    {
                        "$ref": "#/components/parameters/ViewSummary"
                    },
                    {
                        "name": "filter[created_at]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][gte]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][lt]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][gte]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][lt]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/WorkTypeSort"
                    },
                    {
                        "name": "q",
                        "in": "query",
                        "description": "Free-text search over `name`. At most one substring match per request: `q` together with a `contains` filter is 400 invalid-query-parameter.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "minLength": 1
                        }
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "A page of records.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record.",
                                "schema": {
                                    "type": "string",
                                    "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/WorkTypeList"
                                            }
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/WorkTypesIndexMeta"
                                        },
                                        "links": {
                                            "$ref": "#/components/schemas/PageLinks"
                                        }
                                    },
                                    "required": [
                                        "data",
                                        "meta",
                                        "links"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, max-age=60",
                "x-plnnrly-examples": [
                    {
                        "name": "Search work types by name, A→Z",
                        "query": {
                            "q": "bug",
                            "sort": "name"
                        }
                    }
                ],
                "x-plnnrly-feature": "sprints",
                "x-plnnrly-permission": "work_types::read",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1,
                    "view_detail_total": 3,
                    "surcharges": {
                        "q": 2
                    }
                },
                "x-plnnrly-token-ability": [
                    "api:read:work_types"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/WorkTypeList",
                    "detail": "#/components/schemas/WorkType"
                }
            },
            "post": {
                "operationId": "api.v2.work-types.store",
                "summary": "Create a work type",
                "description": "Creates a work-item type and returns it with a `Location` header. `order` is not accepted: the new type is created at position 0 and is reordered only by dragging it in plnnrly. `color` must be six-digit hex and `icon` must be an icon name such as `heroicon-o-tag`, because both are checked again on every read.",
                "tags": [
                    "Configuration"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:work_types"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:work_types"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "description": "Work-type name as shown in the UI, e.g. `Bug`."
                                    },
                                    "color": {
                                        "type": "string",
                                        "pattern": "^#[0-9a-fA-F]{6}\\z",
                                        "maxLength": 255,
                                        "description": "Six-digit hex colour, e.g. `#6b7280`. A stored value that is not six-digit hex reads back as `#6b7280`, so writing this field back unchanged replaces the malformed original."
                                    },
                                    "icon": {
                                        "type": "string",
                                        "pattern": "^[A-Za-z0-9._-]+\\z",
                                        "maxLength": 255,
                                        "description": "Icon name, e.g. `heroicon-o-rectangle-stack`: not a URL and not markup."
                                    }
                                },
                                "required": [
                                    "name",
                                    "color",
                                    "icon"
                                ],
                                "additionalProperties": false
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Created.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "Idempotency-Replayed": {
                                "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise.",
                                "schema": {
                                    "type": "boolean",
                                    "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise."
                                }
                            },
                            "Location": {
                                "description": "The absolute URL of the created or queued resource.",
                                "schema": {
                                    "type": "string",
                                    "description": "The absolute URL of the created or queued resource."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/WorkType"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "$ref": "#/components/responses/Problem409Idempotency"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "A spike work type",
                        "body": {
                            "name": "Spike",
                            "color": "#6b7280",
                            "icon": "heroicon-o-rectangle-stack"
                        }
                    }
                ],
                "x-plnnrly-feature": "sprints",
                "x-plnnrly-permission": "work_types::create",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:work_types"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/WorkTypeList",
                    "detail": "#/components/schemas/WorkType"
                }
            },
            "x-plnnrly-allow": [
                "GET",
                "POST"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/work-types/{work_type}": {
            "get": {
                "operationId": "api.v2.work-types.show",
                "summary": "Read a work type",
                "description": "Returns one work-item type. The summary and detail views are identical on this resource: every field is small enough for a list row, and nothing is includable, so `include` is rejected.",
                "tags": [
                    "Configuration"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:work_types"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:work_types"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "work_type",
                        "in": "path",
                        "description": "The WorkType's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/WorkType"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, max-age=60",
                "x-plnnrly-examples": [
                    {
                        "name": "One work type",
                        "path": {
                            "work_type": 1
                        }
                    }
                ],
                "x-plnnrly-feature": "sprints",
                "x-plnnrly-permission": "work_types::read",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:read:work_types"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/WorkTypeList",
                    "detail": "#/components/schemas/WorkType"
                }
            },
            "patch": {
                "operationId": "api.v2.work-types.update",
                "summary": "Update a work type",
                "description": "Partially updates a work-item type: absent keys are untouched, an empty body is rejected. `order` is not accepted. Send `If-Match` with the `ETag` from your last read to detect a concurrent edit, which answers `412`. Treat it as best-effort only: this resource carries no concurrency token, and `updated_at` is second-precision, so two writes inside the same second are indistinguishable.",
                "tags": [
                    "Configuration"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:work_types"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:work_types"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "work_type",
                        "in": "path",
                        "description": "The WorkType's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "description": "Work-type name as shown in the UI, e.g. `Bug`."
                                    },
                                    "color": {
                                        "type": "string",
                                        "pattern": "^#[0-9a-fA-F]{6}\\z",
                                        "maxLength": 255,
                                        "description": "Six-digit hex colour, e.g. `#6b7280`. A stored value that is not six-digit hex reads back as `#6b7280`, so writing this field back unchanged replaces the malformed original."
                                    },
                                    "icon": {
                                        "type": "string",
                                        "pattern": "^[A-Za-z0-9._-]+\\z",
                                        "maxLength": 255,
                                        "description": "Icon name, e.g. `heroicon-o-rectangle-stack`: not a URL and not markup."
                                    }
                                },
                                "additionalProperties": false,
                                "minProperties": 1
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The updated record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/WorkType"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Recolour a work type",
                        "path": {
                            "work_type": 1
                        },
                        "body": {
                            "color": "#ef4444"
                        },
                        "headers": {
                            "If-Match": "*"
                        }
                    }
                ],
                "x-plnnrly-feature": "sprints",
                "x-plnnrly-permission": "work_types::update",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:work_types"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/WorkTypeList",
                    "detail": "#/components/schemas/WorkType"
                }
            },
            "delete": {
                "operationId": "api.v2.work-types.destroy",
                "summary": "Delete a work type",
                "description": "Permanently deletes a work-item type. This resource is hard-delete: there is no restore action, no `force` parameter and no `trashed` parameter. A type that is still used by work items cannot be deleted: it answers `403` `forbidden-by-policy`, or `404` `resource-not-found` when your token does not also carry `work_types::read`, because a refusal naming a specific id must not confirm that id to a caller who cannot read the collection. Without that refusal, deleting the type would also delete every work item of that type together with its comments, subtasks, links and attachments. Retype the affected work items first, then delete.",
                "tags": [
                    "Configuration"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:destroy:work_types"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "work_type",
                        "in": "path",
                        "description": "The WorkType's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "responses": {
                    "204": {
                        "description": "Deleted. No body.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "description": "Forbidden.\n\n`forbidden-by-policy`: Work items still use this type. Move them to another type first: deleting the type would also delete them, with their comments, subtasks, links and attachments.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "oneOf": [
                                        {
                                            "$ref": "#/components/schemas/ForbiddenByPolicyProblem"
                                        },
                                        {
                                            "$ref": "#/components/schemas/InsufficientPermissionProblem"
                                        },
                                        {
                                            "$ref": "#/components/schemas/InsufficientTokenScopeProblem"
                                        }
                                    ],
                                    "discriminator": {
                                        "propertyName": "type",
                                        "mapping": {
                                            "https://developers.plnnrly.com/api/v2/problems/forbidden-by-policy": "#/components/schemas/ForbiddenByPolicyProblem",
                                            "https://developers.plnnrly.com/api/v2/problems/insufficient-permission": "#/components/schemas/InsufficientPermissionProblem",
                                            "https://developers.plnnrly.com/api/v2/problems/insufficient-token-scope": "#/components/schemas/InsufficientTokenScopeProblem"
                                        }
                                    }
                                },
                                "examples": {
                                    "forbidden-by-policy": {
                                        "$ref": "#/components/examples/ForbiddenByPolicyProblem"
                                    },
                                    "insufficient-permission": {
                                        "$ref": "#/components/examples/InsufficientPermissionProblem"
                                    },
                                    "insufficient-token-scope": {
                                        "$ref": "#/components/examples/InsufficientTokenScopeProblem"
                                    }
                                }
                            }
                        }
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Delete an unused work type",
                        "path": {
                            "work_type": 1
                        }
                    }
                ],
                "x-plnnrly-feature": "sprints",
                "x-plnnrly-permission": "work_types::delete",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:destroy:work_types"
                ]
            },
            "x-plnnrly-allow": [
                "GET",
                "PATCH",
                "DELETE"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/workflow-statuses": {
            "get": {
                "operationId": "api.v2.workflow-statuses.index",
                "summary": "List workflow statuses",
                "description": "Returns the organisation's workflow statuses (the board columns work items sit in) in workflow order, with the id as the tiebreaker. Statuses created through this API are created at position 0, so they tie with each other and sort by id. There is no nested path for one workflow's statuses: scope the collection with `filter[workflow_id]` instead, which is the URL a workflow's `links.statuses` points at. Available only while the Sprints feature is enabled.",
                "tags": [
                    "Configuration"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:workflows"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:workflows"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/PerPage"
                    },
                    {
                        "$ref": "#/components/parameters/Cursor"
                    },
                    {
                        "$ref": "#/components/parameters/WorkflowStatusPageNumber"
                    },
                    {
                        "$ref": "#/components/parameters/ViewSummary"
                    },
                    {
                        "name": "filter[created_at]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][gte]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][lt]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[is_resolution]",
                        "in": "query",
                        "description": "Whether reaching the status resolves the work item.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[updated_at]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][gte]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][lt]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[workflow_id]",
                        "in": "query",
                        "description": "Workflow the status belongs to. This is how you list one workflow's statuses: there is no nested path for them.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[workflow_id][in]",
                        "in": "query",
                        "description": "Workflow the status belongs to. This is how you list one workflow's statuses: there is no nested path for them. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "$ref": "#/components/parameters/WorkflowStatusSort"
                    },
                    {
                        "name": "q",
                        "in": "query",
                        "description": "Free-text search over `name`. At most one substring match per request: `q` together with a `contains` filter is 400 invalid-query-parameter.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "minLength": 1
                        }
                    },
                    {
                        "$ref": "#/components/parameters/WorkflowStatusInclude"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "A page of records.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record.",
                                "schema": {
                                    "type": "string",
                                    "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/WorkflowStatusList"
                                            }
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/WorkflowStatusesIndexMeta"
                                        },
                                        "links": {
                                            "$ref": "#/components/schemas/PageLinks"
                                        }
                                    },
                                    "required": [
                                        "data",
                                        "meta",
                                        "links"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, max-age=60",
                "x-plnnrly-examples": [
                    {
                        "name": "One workflow's open columns, with the workflow expanded",
                        "query": {
                            "filter": {
                                "workflow_id": 4,
                                "is_resolution": false
                            },
                            "include": "workflow"
                        }
                    }
                ],
                "x-plnnrly-feature": "sprints",
                "x-plnnrly-permission": "workflows::read",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1,
                    "view_detail_total": 3,
                    "surcharges": {
                        "include": 1,
                        "q": 2
                    }
                },
                "x-plnnrly-token-ability": [
                    "api:read:workflows"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/WorkflowStatusList",
                    "detail": "#/components/schemas/WorkflowStatus"
                }
            },
            "post": {
                "operationId": "api.v2.workflow-statuses.store",
                "summary": "Create a workflow status",
                "description": "Adds a status to a workflow and returns it with a `Location` header, mirroring the status list in plnnrly's workflow form. `order` is not accepted: the new status is created at position 0 and is reordered only by dragging it in plnnrly. `color` must be six-digit hex, because it is re-validated on every read. A new status is unreachable until you also create the transitions leading into it: a work item can only be moved along an edge the workflow publishes.",
                "tags": [
                    "Configuration"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:workflows"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:workflows"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "workflow_id": {
                                        "type": "integer",
                                        "x-plnnrly-references": {
                                            "resource": "Workflow",
                                            "field": "id"
                                        },
                                        "description": "Workflow this status belongs to. A status cannot be moved to another workflow: its transitions and the work items sitting in it belong to this one."
                                    },
                                    "name": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "description": "Status name as shown on the board column and the status badge, e.g. `To do`."
                                    },
                                    "color": {
                                        "type": "string",
                                        "pattern": "^#[0-9a-fA-F]{6}\\z",
                                        "maxLength": 255,
                                        "description": "Six-digit hex colour, e.g. `#6b7280`. A stored value that is not six-digit hex reads back as `#6b7280`, so writing this field back unchanged replaces the malformed original."
                                    },
                                    "is_resolution": {
                                        "type": "boolean",
                                        "description": "Whether a work item reaching this status counts as resolved. Drives the backlog's done state and what a completed sprint carries over."
                                    }
                                },
                                "required": [
                                    "workflow_id",
                                    "name",
                                    "color"
                                ],
                                "additionalProperties": false
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Created.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "Idempotency-Replayed": {
                                "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise.",
                                "schema": {
                                    "type": "boolean",
                                    "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise."
                                }
                            },
                            "Location": {
                                "description": "The absolute URL of the created or queued resource.",
                                "schema": {
                                    "type": "string",
                                    "description": "The absolute URL of the created or queued resource."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/WorkflowStatus"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "$ref": "#/components/responses/Problem409Idempotency"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "A resolved column",
                        "body": {
                            "workflow_id": 4,
                            "name": "Done",
                            "color": "#22c55e",
                            "is_resolution": true
                        }
                    }
                ],
                "x-plnnrly-feature": "sprints",
                "x-plnnrly-permission": "workflows::create",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:workflows"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/WorkflowStatusList",
                    "detail": "#/components/schemas/WorkflowStatus"
                }
            },
            "x-plnnrly-allow": [
                "GET",
                "POST"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/workflow-statuses/{workflow_status}": {
            "get": {
                "operationId": "api.v2.workflow-statuses.show",
                "summary": "Read a workflow status",
                "description": "Returns one workflow status. The summary and detail views are identical on this resource: every field is small enough for a list row. `workflow` renders as `{\"id\": N}` until named in `include`.",
                "tags": [
                    "Configuration"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:workflows"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:workflows"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "workflow_status",
                        "in": "path",
                        "description": "The WorkflowStatus's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/WorkflowStatus"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, max-age=60",
                "x-plnnrly-examples": [
                    {
                        "name": "One status with its workflow expanded",
                        "path": {
                            "workflow_status": 1
                        },
                        "query": {
                            "include": "workflow"
                        }
                    }
                ],
                "x-plnnrly-feature": "sprints",
                "x-plnnrly-permission": "workflows::read",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:read:workflows"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/WorkflowStatusList",
                    "detail": "#/components/schemas/WorkflowStatus"
                }
            },
            "patch": {
                "operationId": "api.v2.workflow-statuses.update",
                "summary": "Update a workflow status",
                "description": "Partially updates a workflow status: absent keys are untouched, an empty body is rejected. `order` and `workflow_id` are not accepted: a status cannot be moved to another workflow, because its transitions and the work items sitting in it belong to the one it was created in. Flipping `is_resolution` re-decides what the backlog counts as done for every work item already in the status. Send `If-Match` with the `ETag` from your last read to detect a concurrent edit, which answers `412`. Treat it as best-effort only: this resource carries no concurrency token, and `updated_at` is second-precision, so two writes inside the same second are indistinguishable.",
                "tags": [
                    "Configuration"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:workflows"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:workflows"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "workflow_status",
                        "in": "path",
                        "description": "The WorkflowStatus's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "description": "Status name as shown on the board column and the status badge, e.g. `To do`."
                                    },
                                    "color": {
                                        "type": "string",
                                        "pattern": "^#[0-9a-fA-F]{6}\\z",
                                        "maxLength": 255,
                                        "description": "Six-digit hex colour, e.g. `#6b7280`. A stored value that is not six-digit hex reads back as `#6b7280`, so writing this field back unchanged replaces the malformed original."
                                    },
                                    "is_resolution": {
                                        "type": "boolean",
                                        "description": "Whether a work item reaching this status counts as resolved. Drives the backlog's done state and what a completed sprint carries over."
                                    }
                                },
                                "additionalProperties": false,
                                "minProperties": 1
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The updated record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/WorkflowStatus"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Recolour a status",
                        "path": {
                            "workflow_status": 1
                        },
                        "body": {
                            "color": "#ef4444"
                        },
                        "headers": {
                            "If-Match": "*"
                        }
                    }
                ],
                "x-plnnrly-feature": "sprints",
                "x-plnnrly-permission": "workflows::update",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:workflows"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/WorkflowStatusList",
                    "detail": "#/components/schemas/WorkflowStatus"
                }
            },
            "delete": {
                "operationId": "api.v2.workflow-statuses.destroy",
                "summary": "Delete a workflow status",
                "description": "Permanently deletes a workflow status. This resource is hard-delete: there is no restore action, no `force` parameter and no `trashed` parameter. There is no in-use refusal, and the delete reaches further: one call also deletes every work item sitting in this status (with its comments, subtasks, links and attachments) and every transition leading into or out of it. plnnrly itself behaves the same way. Move the affected work items to another status first: list them with GET `/api/v2/work-items?filter[workflow_status_id]={id}`.",
                "tags": [
                    "Configuration"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:destroy:workflows"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "workflow_status",
                        "in": "path",
                        "description": "The WorkflowStatus's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "responses": {
                    "204": {
                        "description": "Deleted. No body.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Remove a status",
                        "path": {
                            "workflow_status": 1
                        }
                    }
                ],
                "x-plnnrly-feature": "sprints",
                "x-plnnrly-permission": "workflows::delete",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:destroy:workflows"
                ]
            },
            "x-plnnrly-allow": [
                "GET",
                "PATCH",
                "DELETE"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/workflow-transitions": {
            "get": {
                "operationId": "api.v2.workflow-transitions.index",
                "summary": "List workflow transitions",
                "description": "Returns the organisation's workflow transitions (the legal status moves work items may make), ordered by id. There is no nested path for one workflow's transitions: scope the collection with `filter[workflow_id]` instead. Available only while the Sprints feature is enabled.",
                "tags": [
                    "Configuration"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:workflows"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:workflows"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/PerPage"
                    },
                    {
                        "$ref": "#/components/parameters/Cursor"
                    },
                    {
                        "$ref": "#/components/parameters/PageNumber"
                    },
                    {
                        "$ref": "#/components/parameters/ViewSummary"
                    },
                    {
                        "name": "filter[created_at]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][gte]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][lt]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[from_status_id]",
                        "in": "query",
                        "description": "Status the edge leads out of.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[from_status_id][in]",
                        "in": "query",
                        "description": "Status the edge leads out of. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[to_status_id]",
                        "in": "query",
                        "description": "Status the edge leads into.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[to_status_id][in]",
                        "in": "query",
                        "description": "Status the edge leads into. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[updated_at]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][gte]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][lt]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[workflow_id]",
                        "in": "query",
                        "description": "Workflow the edge belongs to. This is how you list one workflow's transitions: there is no nested path for them.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[workflow_id][in]",
                        "in": "query",
                        "description": "Workflow the edge belongs to. This is how you list one workflow's transitions: there is no nested path for them. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "$ref": "#/components/parameters/WorkflowTransitionSort"
                    },
                    {
                        "name": "q",
                        "in": "query",
                        "description": "Free-text search over `name`. At most one substring match per request: `q` together with a `contains` filter is 400 invalid-query-parameter.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "minLength": 1
                        }
                    },
                    {
                        "$ref": "#/components/parameters/WorkflowTransitionInclude"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "A page of records.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record.",
                                "schema": {
                                    "type": "string",
                                    "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/WorkflowTransitionList"
                                            }
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/WorkflowTransitionsIndexMeta"
                                        },
                                        "links": {
                                            "$ref": "#/components/schemas/PageLinks"
                                        }
                                    },
                                    "required": [
                                        "data",
                                        "meta",
                                        "links"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, max-age=60",
                "x-plnnrly-examples": [
                    {
                        "name": "One workflow's transitions, with both statuses expanded",
                        "query": {
                            "filter": {
                                "workflow_id": 4
                            },
                            "include": "from_status,to_status"
                        }
                    }
                ],
                "x-plnnrly-feature": "sprints",
                "x-plnnrly-permission": "workflows::read",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1,
                    "view_detail_total": 3,
                    "surcharges": {
                        "include": 1,
                        "q": 2
                    }
                },
                "x-plnnrly-token-ability": [
                    "api:read:workflows"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/WorkflowTransitionList",
                    "detail": "#/components/schemas/WorkflowTransition"
                }
            },
            "post": {
                "operationId": "api.v2.workflow-transitions.store",
                "summary": "Create a workflow transition",
                "description": "Adds a legal status move to a workflow and returns it with a `Location` header, mirroring the \"add transition\" action in plnnrly. Both statuses must belong to the workflow named in `workflow_id`, and they must differ; a status from another workflow is refused as if it did not exist. The three ids are validated together, so an unusable `workflow_id` also fails both status fields. Duplicate edges are legal, exactly as they are in plnnrly. The work-item transition endpoint accepts the new move as soon as this call returns.",
                "tags": [
                    "Configuration"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:workflows"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:workflows"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "workflow_id": {
                                        "type": "integer",
                                        "x-plnnrly-references": {
                                            "resource": "Workflow",
                                            "field": "id"
                                        },
                                        "description": "Workflow this edge belongs to. Both statuses belong to the same workflow, and the edge cannot be moved to another one."
                                    },
                                    "from_status_id": {
                                        "type": "integer",
                                        "x-plnnrly-references": {
                                            "resource": "WorkflowStatus",
                                            "field": "id"
                                        },
                                        "description": "Status a work item must currently be in for this edge to apply."
                                    },
                                    "to_status_id": {
                                        "type": "integer",
                                        "x-plnnrly-constraint": "Must differ from from_status_id.",
                                        "x-plnnrly-references": {
                                            "resource": "WorkflowStatus",
                                            "field": "id"
                                        },
                                        "description": "Status the work item moves to. Always different from `from_status_id`."
                                    },
                                    "name": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "description": "Transition label as shown on the status button, e.g. `Start progress`."
                                    }
                                },
                                "required": [
                                    "workflow_id",
                                    "from_status_id",
                                    "to_status_id",
                                    "name"
                                ],
                                "additionalProperties": false
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Created.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "Idempotency-Replayed": {
                                "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise.",
                                "schema": {
                                    "type": "boolean",
                                    "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise."
                                }
                            },
                            "Location": {
                                "description": "The absolute URL of the created or queued resource.",
                                "schema": {
                                    "type": "string",
                                    "description": "The absolute URL of the created or queued resource."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/WorkflowTransition"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "$ref": "#/components/responses/Problem409Idempotency"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Allow moving from To do to In progress",
                        "body": {
                            "workflow_id": 4,
                            "from_status_id": 11,
                            "to_status_id": 12,
                            "name": "Start progress"
                        }
                    }
                ],
                "x-plnnrly-feature": "sprints",
                "x-plnnrly-permission": "workflows::create",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:workflows"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/WorkflowTransitionList",
                    "detail": "#/components/schemas/WorkflowTransition"
                }
            },
            "x-plnnrly-allow": [
                "GET",
                "POST"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/workflow-transitions/{workflow_transition}": {
            "get": {
                "operationId": "api.v2.workflow-transitions.show",
                "summary": "Read a workflow transition",
                "description": "Returns one workflow transition. The summary and detail views are identical on this resource: every field is small enough for a list row. `workflow`, `from_status` and `to_status` render as `{\"id\": N}` references until named in `include`.",
                "tags": [
                    "Configuration"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:workflows"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:workflows"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "workflow_transition",
                        "in": "path",
                        "description": "The WorkflowTransition's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/WorkflowTransition"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, max-age=60",
                "x-plnnrly-examples": [
                    {
                        "name": "One transition with both statuses expanded",
                        "path": {
                            "workflow_transition": 1
                        },
                        "query": {
                            "include": "from_status,to_status"
                        }
                    }
                ],
                "x-plnnrly-feature": "sprints",
                "x-plnnrly-permission": "workflows::read",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:read:workflows"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/WorkflowTransitionList",
                    "detail": "#/components/schemas/WorkflowTransition"
                }
            },
            "patch": {
                "operationId": "api.v2.workflow-transitions.update",
                "summary": "Update a workflow transition",
                "description": "Partially updates a workflow transition: absent keys are untouched, an empty body is rejected. `workflow_id` is not accepted: an edge cannot be moved to another workflow, because its two statuses belong to the one it was created in. The two status fields must be sent together or not at all, and they must differ; both are checked against the transition's own workflow. Send `If-Match` with the `ETag` from your last read to detect a concurrent edit, which answers `412`. Treat it as best-effort only: this resource carries no concurrency token, and `updated_at` is second-precision, so two writes inside the same second are indistinguishable.",
                "tags": [
                    "Configuration"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:workflows"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:workflows"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "workflow_transition",
                        "in": "path",
                        "description": "The WorkflowTransition's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "description": "Transition label as shown on the status button, e.g. `Start progress`."
                                    },
                                    "from_status_id": {
                                        "type": "integer",
                                        "x-plnnrly-constraint": "Required when to_status_id is present.",
                                        "x-plnnrly-references": {
                                            "resource": "WorkflowStatus",
                                            "field": "id"
                                        },
                                        "description": "Status a work item must currently be in for this edge to apply."
                                    },
                                    "to_status_id": {
                                        "type": "integer",
                                        "x-plnnrly-constraint": "Required when from_status_id is present. Must differ from from_status_id.",
                                        "x-plnnrly-references": {
                                            "resource": "WorkflowStatus",
                                            "field": "id"
                                        },
                                        "description": "Status the work item moves to. Always different from `from_status_id`."
                                    }
                                },
                                "additionalProperties": false,
                                "minProperties": 1
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The updated record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/WorkflowTransition"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Relabel a transition",
                        "path": {
                            "workflow_transition": 1
                        },
                        "body": {
                            "name": "Begin work"
                        },
                        "headers": {
                            "If-Match": "*"
                        }
                    }
                ],
                "x-plnnrly-feature": "sprints",
                "x-plnnrly-permission": "workflows::update",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:workflows"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/WorkflowTransitionList",
                    "detail": "#/components/schemas/WorkflowTransition"
                }
            },
            "delete": {
                "operationId": "api.v2.workflow-transitions.destroy",
                "summary": "Delete a workflow transition",
                "description": "Permanently deletes a workflow transition, mirroring the delete action in plnnrly. This resource is hard-delete: there is no restore action, no `force` parameter and no `trashed` parameter. Work items already sitting on either status are unaffected; they simply lose a legal next move, and the work-item transition endpoint starts refusing it as soon as this call returns.",
                "tags": [
                    "Configuration"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:destroy:workflows"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "workflow_transition",
                        "in": "path",
                        "description": "The WorkflowTransition's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "responses": {
                    "204": {
                        "description": "Deleted. No body.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Remove a transition",
                        "path": {
                            "workflow_transition": 1
                        }
                    }
                ],
                "x-plnnrly-feature": "sprints",
                "x-plnnrly-permission": "workflows::delete",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:destroy:workflows"
                ]
            },
            "x-plnnrly-allow": [
                "GET",
                "PATCH",
                "DELETE"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/workflows": {
            "get": {
                "operationId": "api.v2.workflows.index",
                "summary": "List workflows",
                "description": "Returns the organisation's workflows (the named status sets work items move through), sorted by name A→Z by default, with the id as the tiebreaker. Each row carries `statuses_count` and a `links.statuses` URL; the statuses themselves are never inlined. Available only while the Sprints feature is enabled.",
                "tags": [
                    "Configuration"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:workflows"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:workflows"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/PerPage"
                    },
                    {
                        "$ref": "#/components/parameters/Cursor"
                    },
                    {
                        "$ref": "#/components/parameters/PageNumber"
                    },
                    {
                        "$ref": "#/components/parameters/ViewSummary"
                    },
                    {
                        "name": "filter[created_at]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][gte]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][lt]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][gte]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][lt]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/WorkflowSort"
                    },
                    {
                        "name": "q",
                        "in": "query",
                        "description": "Free-text search over `name`. At most one substring match per request: `q` together with a `contains` filter is 400 invalid-query-parameter.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "minLength": 1
                        }
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "A page of records.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record.",
                                "schema": {
                                    "type": "string",
                                    "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/WorkflowList"
                                            }
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/WorkflowsIndexMeta"
                                        },
                                        "links": {
                                            "$ref": "#/components/schemas/PageLinks"
                                        }
                                    },
                                    "required": [
                                        "data",
                                        "meta",
                                        "links"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Search workflows by name",
                        "query": {
                            "q": "standard"
                        }
                    }
                ],
                "x-plnnrly-feature": "sprints",
                "x-plnnrly-permission": "workflows::read",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1,
                    "view_detail_total": 3,
                    "surcharges": {
                        "q": 2
                    }
                },
                "x-plnnrly-token-ability": [
                    "api:read:workflows"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/WorkflowList",
                    "detail": "#/components/schemas/Workflow"
                }
            },
            "post": {
                "operationId": "api.v2.workflows.store",
                "summary": "Create a workflow",
                "description": "Creates a workflow and returns it with a `Location` header. `name` is the only accepted field: `order` is not writable, so the new workflow is created at position 0 and is reordered only by dragging it in plnnrly. The workflow is created empty (with zero statuses and zero transitions), because statuses and transitions are separate collections: `POST /api/v2/workflow-statuses` with `workflow_id` for each status, then POST `/api/v2/workflow-transitions` to connect them. That sequence is not atomic, so a client that fails partway leaves a workflow with an incomplete status set, and a project pointed at it cannot move work items along the missing transitions.",
                "tags": [
                    "Configuration"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:workflows"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:workflows"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "description": "Workflow name as shown in the UI, e.g. `Standard`."
                                    }
                                },
                                "required": [
                                    "name"
                                ],
                                "additionalProperties": false
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Created.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "Idempotency-Replayed": {
                                "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise.",
                                "schema": {
                                    "type": "boolean",
                                    "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise."
                                }
                            },
                            "Location": {
                                "description": "The absolute URL of the created or queued resource.",
                                "schema": {
                                    "type": "string",
                                    "description": "The absolute URL of the created or queued resource."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Workflow"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "$ref": "#/components/responses/Problem409Idempotency"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "A bug-triage workflow",
                        "body": {
                            "name": "Bug triage"
                        }
                    }
                ],
                "x-plnnrly-feature": "sprints",
                "x-plnnrly-permission": "workflows::create",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:workflows"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/WorkflowList",
                    "detail": "#/components/schemas/Workflow"
                }
            },
            "x-plnnrly-allow": [
                "GET",
                "POST"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/workflows/{workflow}": {
            "get": {
                "operationId": "api.v2.workflows.show",
                "summary": "Read a workflow",
                "description": "Returns one workflow. The summary and detail views are identical on this resource: every field is small enough for a list row, and nothing is includable, so `include` is rejected. Read the workflow's statuses through `links.statuses`, and its transitions through `GET /api/v2/workflow-transitions?filter[workflow_id]={id}`.",
                "tags": [
                    "Configuration"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:workflows"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:workflows"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "workflow",
                        "in": "path",
                        "description": "The Workflow's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Workflow"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "One workflow",
                        "path": {
                            "workflow": 1
                        }
                    }
                ],
                "x-plnnrly-feature": "sprints",
                "x-plnnrly-permission": "workflows::read",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:read:workflows"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/WorkflowList",
                    "detail": "#/components/schemas/Workflow"
                }
            },
            "patch": {
                "operationId": "api.v2.workflows.update",
                "summary": "Update a workflow",
                "description": "Renames a workflow. `name` is the only writable key, so an empty body is rejected and any other key is rejected. Send `If-Match` with the `ETag` from your last read to detect a concurrent edit, which answers `412`. Treat it as best-effort only: this resource carries no concurrency token, and `updated_at` is second-precision, so two writes inside the same second are indistinguishable. Renaming a workflow does not touch its statuses or transitions.",
                "tags": [
                    "Configuration"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:workflows"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:workflows"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "workflow",
                        "in": "path",
                        "description": "The Workflow's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "description": "Workflow name as shown in the UI, e.g. `Standard`."
                                    }
                                },
                                "additionalProperties": false,
                                "minProperties": 1
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The updated record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Workflow"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Rename a workflow",
                        "path": {
                            "workflow": 1
                        },
                        "body": {
                            "name": "Standard (2026)"
                        },
                        "headers": {
                            "If-Match": "*"
                        }
                    }
                ],
                "x-plnnrly-feature": "sprints",
                "x-plnnrly-permission": "workflows::update",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:workflows"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/WorkflowList",
                    "detail": "#/components/schemas/Workflow"
                }
            },
            "delete": {
                "operationId": "api.v2.workflows.destroy",
                "summary": "Delete a workflow",
                "description": "Permanently deletes a workflow. This resource is hard-delete: there is no restore action, no `force` parameter and no `trashed` parameter. This deletes more and is not refused. Every status and every transition in the workflow is deleted with it, and every work item that sat in one of those statuses is deleted as well, taking its subtasks, its links to other work items, its label assignments and its document attachments with it, and detaching the time logged against it. Every project using this workflow is silently detached from it. There is no in-use check and no refusal: a workflow that is driving live projects deletes just as readily as an unused one, which matches plnnrly's equally unguarded delete action. Repoint the affected projects at another workflow first.",
                "tags": [
                    "Configuration"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:destroy:workflows"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "workflow",
                        "in": "path",
                        "description": "The Workflow's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "responses": {
                    "204": {
                        "description": "Deleted. No body.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Delete a workflow and everything on it",
                        "path": {
                            "workflow": 1
                        }
                    }
                ],
                "x-plnnrly-feature": "sprints",
                "x-plnnrly-permission": "workflows::delete",
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:destroy:workflows"
                ]
            },
            "x-plnnrly-allow": [
                "GET",
                "PATCH",
                "DELETE"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/worklogs": {
            "get": {
                "operationId": "api.v2.worklogs.index",
                "summary": "List worklogs",
                "description": "Returns booked hours, newest first by `date`. A caller sees only their own entries unless they hold `times::update_others` or `times::delete_others`, and that narrowing is applied before any client filter, before pagination and before the count, so `filter[user_id]` widens nothing on its own. `meta.page.total` is `null` unless you ask for it with `with_total=1`, which adds 2 read units, because worklogs are usually an organisation's largest collection. `q` matches anywhere in `description`. `project` and `work_item` are on every row already, so naming either in `?include=` is a `400`; `budget`, `task` and `user` are the includable relations, and each additionally requires the read permission and token scope of its own prefix. There is no `duration` string and no `is_in_review` flag: format `minutes` yourself, and read review state as `(exceeded_budget || billable_change_reason) && reviewed_by == null && invoice_id == null`.",
                "tags": [
                    "Time"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:times"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:times"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/PerPage"
                    },
                    {
                        "$ref": "#/components/parameters/Cursor"
                    },
                    {
                        "$ref": "#/components/parameters/ViewSummary"
                    },
                    {
                        "$ref": "#/components/parameters/WithTotal"
                    },
                    {
                        "name": "filter[budget_id]",
                        "in": "query",
                        "description": "Budget the hours are invoiced against.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[budget_id][in]",
                        "in": "query",
                        "description": "Budget the hours are invoiced against. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[budget_id][null]",
                        "in": "query",
                        "description": "Budget the hours are invoiced against. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[created_at]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][gte]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[created_at][lt]",
                        "in": "query",
                        "description": "Creation timestamp. Pair with `sort=id` for a stable full export.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[date]",
                        "in": "query",
                        "description": "The calendar day the work happened. Combine gte and lte for a range.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[date][gte]",
                        "in": "query",
                        "description": "The calendar day the work happened. Combine gte and lte for a range.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[date][lte]",
                        "in": "query",
                        "description": "The calendar day the work happened. Combine gte and lte for a range.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                    },
                    {
                        "name": "filter[exceeded_budget]",
                        "in": "query",
                        "description": "Whether the entry was booked knowing it exceeds its budget.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[invoice_id]",
                        "in": "query",
                        "description": "The invoice these hours were billed on. Use the `null` operator, `filter[invoice_id][null]=true`, to find uninvoiced hours.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[invoice_id][in]",
                        "in": "query",
                        "description": "The invoice these hours were billed on. Use the `null` operator, `filter[invoice_id][null]=true`, to find uninvoiced hours. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[invoice_id][null]",
                        "in": "query",
                        "description": "The invoice these hours were billed on. Use the `null` operator, `filter[invoice_id][null]=true`, to find uninvoiced hours. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[is_not_billable]",
                        "in": "query",
                        "description": "Whether the hours are excluded from invoicing.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[is_overtime]",
                        "in": "query",
                        "description": "Whether the hours count as overtime.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[project_id]",
                        "in": "query",
                        "description": "Project the hours are booked against.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[project_id][in]",
                        "in": "query",
                        "description": "Project the hours are booked against. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[project_id][null]",
                        "in": "query",
                        "description": "Project the hours are booked against. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[reviewed_by]",
                        "in": "query",
                        "description": "The reviewer who settled the entry. Use `filter[reviewed_by][null]=true` to find entries nobody has reviewed.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[reviewed_by][in]",
                        "in": "query",
                        "description": "The reviewer who settled the entry. Use `filter[reviewed_by][null]=true` to find entries nobody has reviewed. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[reviewed_by][null]",
                        "in": "query",
                        "description": "The reviewer who settled the entry. Use `filter[reviewed_by][null]=true` to find entries nobody has reviewed. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[sales_invoice_row_id]",
                        "in": "query",
                        "description": "The sales-invoice line these hours were billed on.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[sales_invoice_row_id][in]",
                        "in": "query",
                        "description": "The sales-invoice line these hours were billed on. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[sales_invoice_row_id][null]",
                        "in": "query",
                        "description": "The sales-invoice line these hours were billed on. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[task_id]",
                        "in": "query",
                        "description": "Task the hours are booked on.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[task_id][in]",
                        "in": "query",
                        "description": "Task the hours are booked on. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[task_id][null]",
                        "in": "query",
                        "description": "Task the hours are booked on. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[updated_at]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][gte]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[updated_at][lt]",
                        "in": "query",
                        "description": "Last-write timestamp. Use for incremental sync.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
                        }
                    },
                    {
                        "name": "filter[user_id]",
                        "in": "query",
                        "description": "Whose hours to return. Accepts the literal `me`, and defaults to the authenticated user. Naming another employee requires the `times::update_others` permission.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[user_id][in]",
                        "in": "query",
                        "description": "Whose hours to return. Accepts the literal `me`, and defaults to the authenticated user. Naming another employee requires the `times::update_others` permission. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[user_id][null]",
                        "in": "query",
                        "description": "Whose hours to return. Accepts the literal `me`, and defaults to the authenticated user. Naming another employee requires the `times::update_others` permission. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "filter[work_item_id]",
                        "in": "query",
                        "description": "Work item the hours are attached to.",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "filter[work_item_id][in]",
                        "in": "query",
                        "description": "Work item the hours are attached to. A comma-separated list of at most 100 values.",
                        "required": false,
                        "schema": {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "maxItems": 100
                        },
                        "style": "form",
                        "explode": false
                    },
                    {
                        "name": "filter[work_item_id][null]",
                        "in": "query",
                        "description": "Work item the hours are attached to. Whether the field is null.",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/TimeSort"
                    },
                    {
                        "name": "q",
                        "in": "query",
                        "description": "Free-text search over `description`. At most one substring match per request: `q` together with a `contains` filter is 400 invalid-query-parameter.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "minLength": 1
                        }
                    },
                    {
                        "$ref": "#/components/parameters/TimeInclude"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "A page of records.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record.",
                                "schema": {
                                    "type": "string",
                                    "description": "A weak validator (`W/\"…\"`) of the rendered page. Usable with `If-None-Match` to skip an unchanged page; never with `If-Match`, because a page is not a record."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/TimeList"
                                            }
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/WorklogsIndexMeta"
                                        },
                                        "links": {
                                            "$ref": "#/components/schemas/PageLinks"
                                        }
                                    },
                                    "required": [
                                        "data",
                                        "meta",
                                        "links"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "My hours for one week, with the task expanded",
                        "query": {
                            "filter[user_id]": "me",
                            "filter[date][gte]": "2026-09-07",
                            "filter[date][lte]": "2026-09-13",
                            "include": "task"
                        }
                    },
                    {
                        "name": "Uninvoiced billable hours on one project, oldest first",
                        "query": {
                            "filter[project_id]": "12",
                            "filter[invoice_id][null]": "true",
                            "filter[is_not_billable]": "false",
                            "sort": "date",
                            "with_total": "1"
                        }
                    }
                ],
                "x-plnnrly-permission": "hours::read",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1,
                    "view_detail_total": 3,
                    "surcharges": {
                        "with_total": 2,
                        "include": 1,
                        "q": 2
                    }
                },
                "x-plnnrly-token-ability": [
                    "api:read:times"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/TimeList",
                    "detail": "#/components/schemas/Time"
                }
            },
            "post": {
                "operationId": "api.v2.worklogs.store",
                "summary": "Log time",
                "description": "Books minutes against a project on one calendar day and returns the entry with a `Location` header. `user_id` is not accepted: the hours belong to the authenticated user unless `on_behalf_of_user_id` names someone else, and logging time for another user additionally requires `times::update_others`, enforced by that field. Every check inside the write (whether that person may log time on the project, owns the task and may log time on the work item) is evaluated against the person the hours are for, not against you, so `is_overtime` is dropped when they lack `times::write_overtime` even if you hold it. One exception, which plnnrly's own form shares: the check that project, task and budget fit together weighs the task against you, so filing hours on somebody else's task needs `times::write_on_others_tasks` from the caller as well. A task or work item that does not belong to the submitted project is a `422` on its own field; a task or work item you may read but may not book against is a `403`. `budget_id` is derived from the task and never accepted. `is_not_billable` is inferred from the task when omitted; sending a value that differs from the inference requires `billable_change_reason`, answering 422 on `/billable_change_reason` without it. Sending a `reason` marks the entry as exceeding its budget whether or not `exceeded_budget` is sent alongside. Either an exceeded budget or a billability override puts the entry in a reviewer's queue, and while it sits there every write to it by anyone but its author or a reviewer answers `409`. `Idempotency-Key` is required here: a retried time entry is real, invoiced damage. The API is stricter than plnnrly here: a task is accepted only if you can also see it on the tasks collection, so a caller holding `times::write_on_others_tasks` but not `kanban::manage_for_others` gets a `422` `exists` for a task plnnrly would accept.",
                "tags": [
                    "Time"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:times"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:times"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IdempotencyKeyRequired"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "project_id": {
                                        "type": "integer",
                                        "x-plnnrly-references": {
                                            "resource": "Project",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "projects",
                                        "description": "Project the hours are booked against; `null` once that project is deleted."
                                    },
                                    "date": {
                                        "type": "string",
                                        "format": "date",
                                        "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                                        "description": "The calendar day the work happened."
                                    },
                                    "minutes": {
                                        "type": "integer",
                                        "minimum": 1,
                                        "maximum": 1440,
                                        "description": "Duration in minutes, between 1 and 1440."
                                    },
                                    "description": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "description": "What the employee worked on."
                                    },
                                    "task_id": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "x-plnnrly-references": {
                                            "resource": "Task",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "kanban",
                                        "description": "Task the hours are booked on; `null` when the entry is not attached to one, or once that task is deleted."
                                    },
                                    "work_item_id": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "x-plnnrly-references": {
                                            "resource": "WorkItem",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "project_management",
                                        "description": "Work item the hours are attached to; `null` when the entry is not attached to one, or once that work item is deleted."
                                    },
                                    "is_overtime": {
                                        "type": "boolean",
                                        "description": "Whether the hours count as overtime. Sending it without the `times::write_overtime` permission leaves it `false` and adds an `input_ignored` warning rather than failing the write."
                                    },
                                    "is_not_billable": {
                                        "type": "boolean",
                                        "description": "Whether these hours are excluded from invoicing. Inferred from the task's `is_billable` flag (`false` without a task) on create and again whenever `task_id` is sent. Sending a value that differs from the inferred or current one requires `billable_change_reason` and puts the entry in a reviewer's queue; without it the write is a `422` on `/billable_change_reason`."
                                    },
                                    "billable_change_reason": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 65535,
                                        "description": "Why the inferred billability was overridden. Required whenever `is_not_billable` is sent with a value that differs from the inferred or current one, on create and on PATCH alike; the override puts the entry in a reviewer's queue, reopening one a reviewer had already settled."
                                    },
                                    "exceeded_budget": {
                                        "type": "boolean",
                                        "description": "Whether the entry was booked knowing it exceeds its budget. Set on create only, and `true` whenever a `reason` is sent; setting it puts the entry in a reviewer's queue."
                                    },
                                    "reason": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 65535,
                                        "description": "Why the budget overrun was accepted. Sending it on create marks the entry as exceeding its budget. Once a reviewer has settled the entry it can no longer be changed: a PATCH that does answers `409` `reason_reviewed`."
                                    },
                                    "on_behalf_of_user_id": {
                                        "type": "integer",
                                        "x-plnnrly-references": {
                                            "resource": "User",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "users",
                                        "description": "Write-only, on create only: the employee to file the hours for. Omit it to file them for yourself. Naming anyone else requires the `times::update_others` permission, and every check inside the write (whether that person may log on the project and on that task, and whether overtime may be recorded) is then evaluated against them, not against you.",
                                        "writeOnly": true
                                    }
                                },
                                "required": [
                                    "project_id",
                                    "date",
                                    "minutes",
                                    "description"
                                ],
                                "additionalProperties": false
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Created.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "Idempotency-Replayed": {
                                "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise.",
                                "schema": {
                                    "type": "boolean",
                                    "description": "`true` when this response was replayed from a stored `Idempotency-Key` rather than produced by a new write. Absent otherwise."
                                }
                            },
                            "Location": {
                                "description": "The absolute URL of the created or queued resource.",
                                "schema": {
                                    "type": "string",
                                    "description": "The absolute URL of the created or queued resource."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Time"
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/WorklogsStoreMeta"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "$ref": "#/components/responses/Problem409Idempotency"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Log two and a half hours on a work item",
                        "body": {
                            "project_id": 12,
                            "work_item_id": 41,
                            "task_id": 88,
                            "date": "2026-09-10",
                            "minutes": 150,
                            "description": "Reviewed the invoice export and fixed the rounding"
                        },
                        "headers": {
                            "Idempotency-Key": "example-key-0001"
                        }
                    },
                    {
                        "name": "Log hours for a colleague, over budget, with a justification",
                        "body": {
                            "project_id": 12,
                            "on_behalf_of_user_id": 34,
                            "date": "2026-09-09",
                            "minutes": 480,
                            "description": "On-site migration support",
                            "reason": "Agreed with the customer by phone; the overrun is billable."
                        },
                        "headers": {
                            "Idempotency-Key": "example-key-0002"
                        }
                    },
                    {
                        "name": "Log hours on a billable task as non-billable, with the reason a reviewer will read",
                        "body": {
                            "project_id": 12,
                            "task_id": 88,
                            "date": "2026-09-11",
                            "minutes": 240,
                            "description": "Reworked the import after our own regression",
                            "is_not_billable": true,
                            "billable_change_reason": "Our regression, not the customer's change request."
                        },
                        "headers": {
                            "Idempotency-Key": "example-key-0003"
                        }
                    }
                ],
                "x-plnnrly-permission": null,
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:times"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/TimeList",
                    "detail": "#/components/schemas/Time"
                }
            },
            "x-plnnrly-allow": [
                "GET",
                "POST"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        },
        "/worklogs/{worklog}": {
            "get": {
                "operationId": "api.v2.worklogs.show",
                "summary": "Read a worklog",
                "description": "Returns one worklog. `reason` and `billable_change_reason` are detail-view fields, so `?view=detail` is what fetches the justifications a list row omits. An entry belonging to another employee is `404` unless the caller holds `times::update_others` or `times::delete_others`: the row is invisible, so its existence stays invisible. `project` and `work_item` are always expanded; `budget`, `task` and `user` expand under `?include=`, and each additionally requires the read permission and token scope of its own prefix.",
                "tags": [
                    "Time"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:read:times"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:read:times"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "worklog",
                        "in": "path",
                        "description": "The Time's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfNoneMatch"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Time"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "304": {
                        "description": "Not modified.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "The validator of the representation you already hold.",
                                "schema": {
                                    "type": "string",
                                    "description": "The validator of the representation you already hold."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "One worklog with its justifications and its budget",
                        "path": {
                            "worklog": 1
                        },
                        "query": {
                            "view": "detail",
                            "include": "budget,task,user"
                        }
                    }
                ],
                "x-plnnrly-permission": "hours::read",
                "x-plnnrly-rate-cost": {
                    "bucket": "read",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:read:times"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/TimeList",
                    "detail": "#/components/schemas/Time"
                }
            },
            "patch": {
                "operationId": "api.v2.worklogs.update",
                "summary": "Correct a worklog",
                "description": "Partially updates a worklog: absent keys are untouched, an empty body is rejected. Changing another user's entry additionally requires `times::update_others`; without it, other people's entries are invisible to you. An entry awaiting review answers `409` with `conflict.reason` `locked_by_review` to anyone but its author or a reviewer, and an invoiced entry answers `409` `locked_by_invoice`: an invoiced entry can never be changed again. `exceeded_budget` is read-only here: it is what puts an entry in the reviewer's queue, and clearing it would withdraw the entry from that queue silently. `reason` stays writable until a reviewer has settled the entry; rewriting it afterwards answers `409` `reason_reviewed`. Sending `task_id` re-infers `is_not_billable` from the task, and sending `is_not_billable` with a value that differs from the inferred or current one requires `billable_change_reason` (`422` on `/billable_change_reason` without it) and puts the entry back in the reviewer's queue, reopening one that was already settled. A new `project_id` must be one the entry's owner holds a task on, and a new `task_id` must be the owner's own task in the entry's project, so a manager holding `times::update_others` may move a colleague's hours onto that colleague's deadline in another project. Only the caller's `times::write_on_others_tasks` widens either rule, never the owner's; a refusal answers `403` `forbidden-by-policy`, and a task outside the project a `422` on `/task_id`. Moving the entry with `project_id` detaches a task or work item that cannot follow it and nulls `budget_id` with the task. Reassigning a worklog to another employee is not possible: that is a DELETE plus a POST. `work_item_id` is checked for existence, visibility and the same project, and moving the entry onto a different work item additionally requires that the entry's owner may log time on it, the same rule plnnrly and the create operation apply; otherwise it answers `403` `forbidden-by-policy`. Send `If-Match` with the `ETag` from your last read to detect a concurrent edit, which answers `412`. Treat it as best-effort only: this resource carries no concurrency token, and `updated_at` is second-precision.",
                "tags": [
                    "Time"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:write:times"
                        ]
                    },
                    {
                        "oauth2": [
                            "api:write:times"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "worklog",
                        "in": "path",
                        "description": "The Time's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/ViewDetail"
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "date": {
                                        "type": "string",
                                        "format": "date",
                                        "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                                        "description": "The calendar day the work happened."
                                    },
                                    "minutes": {
                                        "type": "integer",
                                        "minimum": 1,
                                        "maximum": 1440,
                                        "description": "Duration in minutes, between 1 and 1440."
                                    },
                                    "description": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "description": "What the employee worked on."
                                    },
                                    "project_id": {
                                        "type": "integer",
                                        "x-plnnrly-references": {
                                            "resource": "Project",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "projects",
                                        "description": "Project the hours are booked against; `null` once that project is deleted."
                                    },
                                    "task_id": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "x-plnnrly-references": {
                                            "resource": "Task",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "kanban",
                                        "description": "Task the hours are booked on; `null` when the entry is not attached to one, or once that task is deleted."
                                    },
                                    "work_item_id": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ],
                                        "x-plnnrly-references": {
                                            "resource": "WorkItem",
                                            "field": "id"
                                        },
                                        "x-plnnrly-references-prefix": "project_management",
                                        "description": "Work item the hours are attached to; `null` when the entry is not attached to one, or once that work item is deleted."
                                    },
                                    "is_overtime": {
                                        "type": "boolean",
                                        "description": "Whether the hours count as overtime. Sending it without the `times::write_overtime` permission leaves it `false` and adds an `input_ignored` warning rather than failing the write."
                                    },
                                    "is_not_billable": {
                                        "type": "boolean",
                                        "description": "Whether these hours are excluded from invoicing. Inferred from the task's `is_billable` flag (`false` without a task) on create and again whenever `task_id` is sent. Sending a value that differs from the inferred or current one requires `billable_change_reason` and puts the entry in a reviewer's queue; without it the write is a `422` on `/billable_change_reason`."
                                    },
                                    "billable_change_reason": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 65535,
                                        "description": "Why the inferred billability was overridden. Required whenever `is_not_billable` is sent with a value that differs from the inferred or current one, on create and on PATCH alike; the override puts the entry in a reviewer's queue, reopening one a reviewer had already settled."
                                    },
                                    "reason": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 65535,
                                        "description": "Why the budget overrun was accepted. Sending it on create marks the entry as exceeding its budget. Once a reviewer has settled the entry it can no longer be changed: a PATCH that does answers `409` `reason_reviewed`."
                                    }
                                },
                                "additionalProperties": false,
                                "minProperties": 1
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The updated record.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "ETag": {
                                "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`.",
                                "schema": {
                                    "type": "string",
                                    "description": "A strong validator of exactly this representation. Send it back as `If-Match` on a later write for optimistic concurrency, or as `If-None-Match` on a re-read to get a `304`."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/Time"
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/WorklogsUpdateMeta"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "description": "Forbidden.\n\n`forbidden-by-policy`: You point the entry at something its owner may not log time on: a `project_id` where the owner holds no task, a `task_id` that is not one of the owner's own tasks, or a `work_item_id` the owner may not log time on. Holding `times::write_on_others_tasks` lifts the first two rules, never the third. You also get this answer on a colleague's entry when you hold `times::delete_others` but not `times::update_others`: either permission lets you see it, only `times::update_others` lets you change it.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "oneOf": [
                                        {
                                            "$ref": "#/components/schemas/ForbiddenByPolicyProblem"
                                        },
                                        {
                                            "$ref": "#/components/schemas/InsufficientPermissionProblem"
                                        },
                                        {
                                            "$ref": "#/components/schemas/InsufficientTokenScopeProblem"
                                        }
                                    ],
                                    "discriminator": {
                                        "propertyName": "type",
                                        "mapping": {
                                            "https://developers.plnnrly.com/api/v2/problems/forbidden-by-policy": "#/components/schemas/ForbiddenByPolicyProblem",
                                            "https://developers.plnnrly.com/api/v2/problems/insufficient-permission": "#/components/schemas/InsufficientPermissionProblem",
                                            "https://developers.plnnrly.com/api/v2/problems/insufficient-token-scope": "#/components/schemas/InsufficientTokenScopeProblem"
                                        }
                                    }
                                },
                                "examples": {
                                    "forbidden-by-policy": {
                                        "$ref": "#/components/examples/ForbiddenByPolicyProblem"
                                    },
                                    "insufficient-permission": {
                                        "$ref": "#/components/examples/InsufficientPermissionProblem"
                                    },
                                    "insufficient-token-scope": {
                                        "$ref": "#/components/examples/InsufficientTokenScopeProblem"
                                    }
                                }
                            }
                        }
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "description": "Conflict.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/StateConflictProblem"
                                },
                                "examples": {
                                    "state-conflict": {
                                        "$ref": "#/components/examples/StateConflictProblem"
                                    }
                                }
                            }
                        }
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "415": {
                        "$ref": "#/components/responses/Problem415"
                    },
                    "422": {
                        "$ref": "#/components/responses/Problem422"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Correct the minutes and the description",
                        "path": {
                            "worklog": 1
                        },
                        "body": {
                            "minutes": 90,
                            "description": "Reviewed the invoice export (rounding fix split out)"
                        },
                        "headers": {
                            "If-Match": "*"
                        }
                    },
                    {
                        "name": "Mark the entry non-billable, which sends it to a reviewer",
                        "path": {
                            "worklog": 1
                        },
                        "body": {
                            "is_not_billable": true,
                            "billable_change_reason": "Internal rework, not for the customer."
                        }
                    }
                ],
                "x-plnnrly-permission": null,
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:write:times"
                ],
                "x-plnnrly-view-schemas": {
                    "summary": "#/components/schemas/TimeList",
                    "detail": "#/components/schemas/Time"
                }
            },
            "delete": {
                "operationId": "api.v2.worklogs.destroy",
                "summary": "Delete a worklog",
                "description": "Permanently deletes a worklog, like the delete action in plnnrly. This resource is hard-delete: there is no restore action, no `force` parameter, no `trashed` parameter and no deletion signal for a sync client, so a client tracking hours learns of a deletion only by the row's absence. Deleting another user's entry additionally requires `times::delete_others`; note it is `delete_others` here and `update_others` on PATCH. Either permission makes colleagues' entries visible, so a caller holding only `update_others` sees a colleague's entry and gets `403` `forbidden-by-policy` here. An entry awaiting review answers `409` `locked_by_review` to anyone but its author or a reviewer, and an invoiced entry answers `409` `locked_by_invoice`: invoiced hours can never be deleted, because the worklog would then diverge from what the customer was billed.",
                "tags": [
                    "Time"
                ],
                "security": [
                    {
                        "bearerToken": [
                            "api:destroy:times"
                        ]
                    }
                ],
                "parameters": [
                    {
                        "name": "worklog",
                        "in": "path",
                        "description": "The Time's numeric id.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "$ref": "#/components/parameters/XRequestId"
                    },
                    {
                        "$ref": "#/components/parameters/IfMatch"
                    }
                ],
                "responses": {
                    "204": {
                        "description": "Deleted. No body.",
                        "headers": {
                            "Cache-Control": {
                                "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable.",
                                "schema": {
                                    "type": "string",
                                    "description": "`private, no-store` unless the operation declares a narrower one. Responses carry your organisation's data behind a bearer token, so they are never publicly cacheable."
                                }
                            },
                            "RateLimit": {
                                "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched.",
                                "schema": {
                                    "type": "string",
                                    "description": "The IETF `httpapi-ratelimit-headers` form of your remaining allowance on the tightest bucket this request touched."
                                }
                            },
                            "RateLimit-Policy": {
                                "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The ceiling and window of that bucket, as `<limit>;w=<seconds>`."
                                }
                            },
                            "Vary": {
                                "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.",
                                "schema": {
                                    "type": "string",
                                    "description": "The request headers this representation depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another."
                                }
                            },
                            "X-Content-Type-Options": {
                                "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app.",
                                "schema": {
                                    "type": "string",
                                    "description": "`nosniff` on every response, problem documents included: the API echoes caller-supplied parameter and body keys verbatim into `application/problem+json` on the same origin as the signed-in plnnrly app."
                                }
                            },
                            "X-Plnnrly-Api-Version": {
                                "description": "The major version serving this response. Always `2` on `/api/v2`.",
                                "schema": {
                                    "type": "string",
                                    "description": "The major version serving this response. Always `2` on `/api/v2`."
                                }
                            },
                            "X-RateLimit-Limit": {
                                "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the bucket ceiling. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Remaining": {
                                "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of the remaining allowance. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-RateLimit-Reset": {
                                "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                "schema": {
                                    "type": "integer",
                                    "description": "Legacy mirror of when the bucket refills, as a Unix timestamp; `RateLimit`'s `reset` gives the same moment in seconds from now. Read `RateLimit` instead.",
                                    "deprecated": true,
                                    "x-plnnrly-sunset": "2027-09-01"
                                },
                                "deprecated": true
                            },
                            "X-Request-Id": {
                                "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support.",
                                "schema": {
                                    "type": "string",
                                    "description": "The server-generated ULID correlating this response with our logs. Always the server's own value, never the `X-Request-Id` you sent, which is only logged. Quote it to support."
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Problem400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Problem401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Problem403"
                    },
                    "404": {
                        "$ref": "#/components/responses/Problem404"
                    },
                    "406": {
                        "$ref": "#/components/responses/Problem406"
                    },
                    "409": {
                        "description": "Conflict.",
                        "content": {
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/StateConflictProblem"
                                },
                                "examples": {
                                    "state-conflict": {
                                        "$ref": "#/components/examples/StateConflictProblem"
                                    }
                                }
                            }
                        }
                    },
                    "412": {
                        "$ref": "#/components/responses/Problem412"
                    },
                    "429": {
                        "$ref": "#/components/responses/Problem429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Problem500"
                    },
                    "503": {
                        "$ref": "#/components/responses/Problem503"
                    }
                },
                "x-plnnrly-cache": "private, no-store",
                "x-plnnrly-examples": [
                    {
                        "name": "Delete a worklog",
                        "path": {
                            "worklog": 1
                        }
                    }
                ],
                "x-plnnrly-permission": null,
                "x-plnnrly-rate-cost": {
                    "bucket": "write",
                    "tier": "api",
                    "base": 1
                },
                "x-plnnrly-token-ability": [
                    "api:destroy:times"
                ]
            },
            "x-plnnrly-allow": [
                "GET",
                "PATCH",
                "DELETE"
            ],
            "x-plnnrly-path-responses": {
                "405": {
                    "$ref": "#/components/responses/Problem405"
                }
            }
        }
    },
    "components": {
        "examples": {
            "DuplicateResourceProblem": {
                "value": {
                    "type": "https://developers.plnnrly.com/api/v2/problems/duplicate-resource",
                    "title": "Duplicate resource",
                    "status": 409,
                    "detail": "Another project already uses the code \"ACM\".",
                    "instance": "/api/v2/projects",
                    "request_id": "01K4B7Q9YV3PZ2N8T1E6M0XR4S",
                    "conflict": {
                        "field": "code",
                        "value": "ACM"
                    }
                }
            },
            "EndpointNotFoundProblem": {
                "value": {
                    "type": "https://developers.plnnrly.com/api/v2/problems/endpoint-not-found",
                    "title": "Endpoint not found",
                    "status": 404,
                    "detail": "No API endpoint matches this request.",
                    "instance": "/api/v2/nope",
                    "request_id": "01K4B7Q9YV3PZ2N8T1E6M0XR4S"
                }
            },
            "EndpointRetiredProblem": {
                "value": {
                    "type": "https://developers.plnnrly.com/api/v2/problems/endpoint-retired",
                    "title": "Endpoint retired",
                    "status": 410,
                    "detail": "This endpoint has been retired and no longer serves requests.",
                    "instance": "/api/v2/projects/4711/statistics",
                    "request_id": "01K4B7Q9YV3PZ2N8T1E6M0XR4S",
                    "sunset": "2027-09-01",
                    "replaced_by": "api.v2.projects.show"
                }
            },
            "ForbiddenByPolicyProblem": {
                "value": {
                    "type": "https://developers.plnnrly.com/api/v2/problems/forbidden-by-policy",
                    "title": "Forbidden by policy",
                    "status": 403,
                    "detail": "You may not perform this operation on this record.",
                    "instance": "/api/v2/projects/4711",
                    "request_id": "01K4B7Q9YV3PZ2N8T1E6M0XR4S"
                }
            },
            "IdempotencyKeyInFlightProblem": {
                "value": {
                    "type": "https://developers.plnnrly.com/api/v2/problems/idempotency-key-in-flight",
                    "title": "Idempotency key in flight",
                    "status": 409,
                    "detail": "The original request for this Idempotency-Key is still being processed. Retry shortly.",
                    "instance": "/api/v2/worklogs",
                    "request_id": "01K4B7Q9YV3PZ2N8T1E6M0XR4S"
                }
            },
            "IdempotencyKeyRequiredProblem": {
                "value": {
                    "type": "https://developers.plnnrly.com/api/v2/problems/idempotency-key-required",
                    "title": "Idempotency key required",
                    "status": 400,
                    "detail": "This endpoint requires an Idempotency-Key header.",
                    "instance": "/api/v2/worklogs",
                    "request_id": "01K4B7Q9YV3PZ2N8T1E6M0XR4S"
                }
            },
            "IdempotencyKeyReusedProblem": {
                "value": {
                    "type": "https://developers.plnnrly.com/api/v2/problems/idempotency-key-reused",
                    "title": "Idempotency key reused",
                    "status": 409,
                    "detail": "This Idempotency-Key was already used with a different request body.",
                    "instance": "/api/v2/worklogs",
                    "request_id": "01K4B7Q9YV3PZ2N8T1E6M0XR4S"
                }
            },
            "InsufficientPermissionProblem": {
                "value": {
                    "type": "https://developers.plnnrly.com/api/v2/problems/insufficient-permission",
                    "title": "Insufficient permission",
                    "status": 403,
                    "detail": "Your role does not carry the permission this endpoint requires.",
                    "instance": "/api/v2/projects/4711",
                    "request_id": "01K4B7Q9YV3PZ2N8T1E6M0XR4S",
                    "required_permission": "projects::read"
                }
            },
            "InsufficientTokenScopeProblem": {
                "value": {
                    "type": "https://developers.plnnrly.com/api/v2/problems/insufficient-token-scope",
                    "title": "Insufficient token scope",
                    "status": 403,
                    "detail": "This token does not carry an ability that covers this endpoint.",
                    "instance": "/api/v2/drive-documents/91",
                    "request_id": "01K4B7Q9YV3PZ2N8T1E6M0XR4S",
                    "required_ability": [
                        "api:read:project_drive",
                        "api:read:customer_drive"
                    ]
                }
            },
            "InternalErrorProblem": {
                "value": {
                    "type": "https://developers.plnnrly.com/api/v2/problems/internal-error",
                    "title": "Internal error",
                    "status": 500,
                    "detail": "An unexpected error occurred. Quote the request_id when contacting support.",
                    "instance": "/api/v2/projects",
                    "request_id": "01K4B7Q9YV3PZ2N8T1E6M0XR4S"
                }
            },
            "InvalidCursorProblem": {
                "value": {
                    "type": "https://developers.plnnrly.com/api/v2/problems/invalid-cursor",
                    "title": "Invalid cursor",
                    "status": 400,
                    "detail": "The cursor is not valid for this query. Restart the scan without a cursor.",
                    "instance": "/api/v2/projects",
                    "request_id": "01K4B7Q9YV3PZ2N8T1E6M0XR4S"
                }
            },
            "InvalidQueryParameterProblem": {
                "value": {
                    "type": "https://developers.plnnrly.com/api/v2/problems/invalid-query-parameter",
                    "title": "Invalid query parameter",
                    "status": 400,
                    "detail": "1 query parameter was not recognised or not permitted on this resource.",
                    "instance": "/api/v2/projects",
                    "request_id": "01K4B7Q9YV3PZ2N8T1E6M0XR4S",
                    "errors": [
                        {
                            "parameter": "limit",
                            "code": "unknown_parameter",
                            "detail": "Unknown query parameter \"limit\". Allowed: filter[…], sort, include, q, trashed, per_page, cursor, page, with_total, view, meta, force, url_ttl_minutes."
                        }
                    ]
                }
            },
            "MalformedRequestProblem": {
                "value": {
                    "type": "https://developers.plnnrly.com/api/v2/problems/malformed-request",
                    "title": "Malformed request",
                    "status": 400,
                    "detail": "The request could not be read.",
                    "instance": "/api/v2/worklogs",
                    "request_id": "01K4B7Q9YV3PZ2N8T1E6M0XR4S"
                }
            },
            "MethodNotAllowedProblem": {
                "value": {
                    "type": "https://developers.plnnrly.com/api/v2/problems/method-not-allowed",
                    "title": "Method not allowed",
                    "status": 405,
                    "detail": "PUT is not supported. This API patches: use PATCH with only the fields you are changing.",
                    "instance": "/api/v2/projects/4711",
                    "request_id": "01K4B7Q9YV3PZ2N8T1E6M0XR4S"
                }
            },
            "NotAcceptableProblem": {
                "value": {
                    "type": "https://developers.plnnrly.com/api/v2/problems/not-acceptable",
                    "title": "Not acceptable",
                    "status": 406,
                    "detail": "This API only produces application/json. Send an Accept header that allows it.",
                    "instance": "/api/v2/projects",
                    "request_id": "01K4B7Q9YV3PZ2N8T1E6M0XR4S"
                }
            },
            "PreconditionFailedProblem": {
                "value": {
                    "type": "https://developers.plnnrly.com/api/v2/problems/precondition-failed",
                    "title": "Precondition failed",
                    "status": 412,
                    "detail": "The record changed since the version named in If-Match. Re-read it and retry.",
                    "instance": "/api/v2/work-items/812",
                    "request_id": "01K4B7Q9YV3PZ2N8T1E6M0XR4S",
                    "current_version": 12
                }
            },
            "RateLimitExceededProblem": {
                "value": {
                    "type": "https://developers.plnnrly.com/api/v2/problems/rate-limit-exceeded",
                    "title": "Rate limit exceeded",
                    "status": 429,
                    "detail": "The rate limit for this caller has been exhausted. Retry after the interval in the Retry-After header.",
                    "instance": "/api/v2/projects",
                    "request_id": "01K4B7Q9YV3PZ2N8T1E6M0XR4S",
                    "rate_limit": {
                        "limit": 600,
                        "retry_after": 42
                    }
                }
            },
            "ResourceNotFoundProblem": {
                "value": {
                    "type": "https://developers.plnnrly.com/api/v2/problems/resource-not-found",
                    "title": "Resource not found",
                    "status": 404,
                    "detail": "No Project was found with id 4711.",
                    "instance": "/api/v2/projects/4711",
                    "request_id": "01K4B7Q9YV3PZ2N8T1E6M0XR4S"
                }
            },
            "StateConflictProblem": {
                "value": {
                    "type": "https://developers.plnnrly.com/api/v2/problems/state-conflict",
                    "title": "State conflict",
                    "status": 409,
                    "detail": "The target status is not reachable from this work item's current status.",
                    "instance": "/api/v2/work-items/812/actions/transition",
                    "request_id": "01K4B7Q9YV3PZ2N8T1E6M0XR4S",
                    "conflict": {
                        "reason": "illegal_transition",
                        "from_workflow_status_id": 3,
                        "to_workflow_status_id": 9
                    }
                }
            },
            "TenantUnavailableProblem": {
                "value": {
                    "type": "https://developers.plnnrly.com/api/v2/problems/tenant-unavailable",
                    "title": "Tenant unavailable",
                    "status": 503,
                    "detail": "This organisation is provisioning, suspended or archived, or the service is down for maintenance.",
                    "instance": "/api/v2/projects",
                    "request_id": "01K4B7Q9YV3PZ2N8T1E6M0XR4S"
                }
            },
            "TokenExpiredProblem": {
                "value": {
                    "type": "https://developers.plnnrly.com/api/v2/problems/token-expired",
                    "title": "Token expired",
                    "status": 401,
                    "detail": "This token has expired. Create a new one under API access in your user menu.",
                    "instance": "/api/v2/projects",
                    "request_id": "01K4B7Q9YV3PZ2N8T1E6M0XR4S",
                    "expired_at": "2026-08-01T09:00:00Z"
                }
            },
            "UnauthenticatedProblem": {
                "value": {
                    "type": "https://developers.plnnrly.com/api/v2/problems/unauthenticated",
                    "title": "Unauthenticated",
                    "status": 401,
                    "detail": "No valid bearer token was supplied.",
                    "instance": "/api/v2/projects",
                    "request_id": "01K4B7Q9YV3PZ2N8T1E6M0XR4S"
                }
            },
            "UnprocessableRelationshipProblem": {
                "value": {
                    "type": "https://developers.plnnrly.com/api/v2/problems/unprocessable-relationship",
                    "title": "Unprocessable relationship",
                    "status": 422,
                    "detail": "1 referenced record cannot be used here.",
                    "instance": "/api/v2/work-items",
                    "request_id": "01K4B7Q9YV3PZ2N8T1E6M0XR4S",
                    "errors": [
                        {
                            "pointer": "/sprint_id",
                            "code": "exists",
                            "detail": "Sprint 55 belongs to another project and cannot be used here."
                        }
                    ]
                }
            },
            "UnsupportedMediaTypeProblem": {
                "value": {
                    "type": "https://developers.plnnrly.com/api/v2/problems/unsupported-media-type",
                    "title": "Unsupported media type",
                    "status": 415,
                    "detail": "This API only accepts application/json request bodies.",
                    "instance": "/api/v2/worklogs",
                    "request_id": "01K4B7Q9YV3PZ2N8T1E6M0XR4S"
                }
            },
            "UnsupportedParameterProblem": {
                "value": {
                    "type": "https://developers.plnnrly.com/api/v2/problems/unsupported-parameter",
                    "title": "Unsupported parameter",
                    "status": 400,
                    "detail": "1 query parameter is not supported on this endpoint.",
                    "instance": "/api/v2/projects",
                    "request_id": "01K4B7Q9YV3PZ2N8T1E6M0XR4S",
                    "errors": [
                        {
                            "parameter": "fields",
                            "code": "unsupported_parameter",
                            "detail": "Sparse fieldsets are not supported by this API. Use view=summary or view=detail instead."
                        }
                    ]
                }
            },
            "ValidationFailedProblem": {
                "value": {
                    "type": "https://developers.plnnrly.com/api/v2/problems/validation-failed",
                    "title": "Validation failed",
                    "status": 422,
                    "detail": "3 fields failed validation.",
                    "instance": "/api/v2/worklogs",
                    "request_id": "01K4B7Q9YV3PZ2N8T1E6M0XR4S",
                    "errors": [
                        {
                            "pointer": "/date",
                            "code": "date_format",
                            "detail": "The date does not match the format Y-m-d."
                        },
                        {
                            "pointer": "/minutes",
                            "code": "min",
                            "detail": "The minutes must be at least 1."
                        },
                        {
                            "pointer": "/description",
                            "code": "required",
                            "detail": "The description field is required."
                        }
                    ]
                }
            }
        },
        "parameters": {
            "AbsenceInclude": {
                "name": "include",
                "in": "query",
                "description": "Comma-separated names to inline, at most five. Each is a single name from the list below, never a dotted path: nested includes are not supported. `manager`: The manager who filed or approved the absence. `user`: The employee the absence is for.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^(manager|user)(,(manager|user)){0,4}$"
                },
                "x-plnnrly-include-paths": {
                    "manager": {
                        "description": "The manager who filed or approved the absence.",
                        "kind": "relation",
                        "to_many": false
                    },
                    "user": {
                        "description": "The employee the absence is for.",
                        "kind": "relation",
                        "to_many": false
                    }
                }
            },
            "AbsenceSort": {
                "name": "sort",
                "in": "query",
                "description": "Up to three keys, comma-separated, each optionally prefixed with `-` for descending. `start`: When the absence begins, newest first. `end`: When the absence ends. `id`: Id, ascending. An id never changes, so use this for a stable full export. `updated_at`: Last-write timestamp; pair with the id tiebreaker for sync.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^-?(start|end|id|updated_at)(,-?(start|end|id|updated_at)){0,2}$",
                    "default": "start"
                },
                "x-plnnrly-sort-keys": {
                    "start": "When the absence begins, newest first.",
                    "end": "When the absence ends.",
                    "id": "Id, ascending. An id never changes, so use this for a stable full export.",
                    "updated_at": "Last-write timestamp; pair with the id tiebreaker for sync."
                }
            },
            "BudgetGroupSort": {
                "name": "sort",
                "in": "query",
                "description": "Up to three keys, comma-separated, each optionally prefixed with `-` for descending. `name`: Budget group name, A→Z. `id`: Id, ascending. An id never changes, so use this for a stable full export. `updated_at`: Last-write timestamp; pair with the id tiebreaker for sync.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^-?(name|id|updated_at)(,-?(name|id|updated_at)){0,2}$",
                    "default": "name"
                },
                "x-plnnrly-sort-keys": {
                    "name": "Budget group name, A→Z.",
                    "id": "Id, ascending. An id never changes, so use this for a stable full export.",
                    "updated_at": "Last-write timestamp; pair with the id tiebreaker for sync."
                }
            },
            "BudgetPageNumber": {
                "name": "page",
                "in": "query",
                "description": "Opt in to page-number paging: 1-based, at most 1000. Omit it, the default, to page by opaque cursor, which is what links.next hands you. Cannot be combined with cursor. Published only on collections that count their rows on every request. This collection's default sort is a display position that changes whenever someone drags the list into a new order in plnnrly, so a page number can skip or repeat a row if the list is reordered between two page requests. These collections rarely exceed one page; for a stable walk, send sort=id and follow links.next without page.",
                "required": false,
                "schema": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 1000
                },
                "x-plnnrly-constraint": "This collection's default sort is a display position that changes whenever someone drags the list into a new order in plnnrly, so a page number can skip or repeat a row if the list is reordered between two page requests. These collections rarely exceed one page; for a stable walk, send sort=id and follow links.next without page."
            },
            "BudgetSort": {
                "name": "sort",
                "in": "query",
                "description": "Up to three keys, comma-separated, each optionally prefixed with `-` for descending. `order_column`: Position within the project as arranged in plnnrly, lowest first. The values are numbered across the whole organisation and renumbered per project, so a page of budgets from several projects interleaves and ties are broken by id. This position changes whenever someone drags the list into a new order in plnnrly, so paging by number over it can skip or repeat a row. `start_date`: First day the budget covers. `end_date`: Last day the budget covers. `id`: Id, ascending. An id never changes, so use this for a stable full export. `updated_at`: Last-write timestamp; pair with the id tiebreaker for sync.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^-?(order_column|start_date|end_date|id|updated_at)(,-?(order_column|start_date|end_date|id|updated_at)){0,2}$",
                    "default": "order_column"
                },
                "x-plnnrly-sort-keys": {
                    "order_column": "Position within the project as arranged in plnnrly, lowest first. The values are numbered across the whole organisation and renumbered per project, so a page of budgets from several projects interleaves and ties are broken by id.",
                    "start_date": "First day the budget covers.",
                    "end_date": "Last day the budget covers.",
                    "id": "Id, ascending. An id never changes, so use this for a stable full export.",
                    "updated_at": "Last-write timestamp; pair with the id tiebreaker for sync."
                }
            },
            "CheckinInclude": {
                "name": "include",
                "in": "query",
                "description": "Comma-separated names to inline, at most five. Each is a single name from the list below, never a dotted path: nested includes are not supported. `user`: The employee this checkin belongs to.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^(user)(,(user)){0,4}$"
                },
                "x-plnnrly-include-paths": {
                    "user": {
                        "description": "The employee this checkin belongs to.",
                        "kind": "relation",
                        "to_many": false
                    }
                }
            },
            "CheckinSort": {
                "name": "sort",
                "in": "query",
                "description": "Up to three keys, comma-separated, each optionally prefixed with `-` for descending. `created_at`: When the checkin row was created, newest first. `checked_in_at`: When the employee clocked in. `checked_out_at`: When the employee clocked out; the still-open checkins sort with the nulls. `id`: Id, ascending. An id never changes, so use this for a stable full export. `updated_at`: Last-write timestamp; pair with the id tiebreaker for sync.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^-?(created_at|checked_in_at|checked_out_at|id|updated_at)(,-?(created_at|checked_in_at|checked_out_at|id|updated_at)){0,2}$",
                    "default": "created_at"
                },
                "x-plnnrly-sort-keys": {
                    "created_at": "When the checkin row was created, newest first.",
                    "checked_in_at": "When the employee clocked in.",
                    "checked_out_at": "When the employee clocked out; the still-open checkins sort with the nulls.",
                    "id": "Id, ascending. An id never changes, so use this for a stable full export.",
                    "updated_at": "Last-write timestamp; pair with the id tiebreaker for sync."
                }
            },
            "CommentInclude": {
                "name": "include",
                "in": "query",
                "description": "Comma-separated names to inline, at most five. Each is a single name from the list below, never a dotted path: nested includes are not supported. `author`: Expand the author into a name-and-id object instead of a bare reference.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^(author)(,(author)){0,4}$"
                },
                "x-plnnrly-include-paths": {
                    "author": {
                        "description": "Expand the author into a name-and-id object instead of a bare reference.",
                        "kind": "relation",
                        "to_many": false
                    }
                }
            },
            "CommentSort": {
                "name": "sort",
                "in": "query",
                "description": "Up to three keys, comma-separated, each optionally prefixed with `-` for descending. `created_at`: When the comment was posted, newest first. `id`: Id, ascending. An id never changes, so use this for a stable full export. `updated_at`: Last-write timestamp; pair with the id tiebreaker for sync.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^-?(created_at|id|updated_at)(,-?(created_at|id|updated_at)){0,2}$",
                    "default": "created_at"
                },
                "x-plnnrly-sort-keys": {
                    "created_at": "When the comment was posted, newest first.",
                    "id": "Id, ascending. An id never changes, so use this for a stable full export.",
                    "updated_at": "Last-write timestamp; pair with the id tiebreaker for sync."
                }
            },
            "CompanyBranchSort": {
                "name": "sort",
                "in": "query",
                "description": "Up to three keys, comma-separated, each optionally prefixed with `-` for descending. `name`: Branch name, A→Z. `id`: Id, ascending. An id never changes, so use this for a stable full export. `updated_at`: Last-write timestamp; pair with the id tiebreaker for sync.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^-?(name|id|updated_at)(,-?(name|id|updated_at)){0,2}$",
                    "default": "name"
                },
                "x-plnnrly-sort-keys": {
                    "name": "Branch name, A→Z.",
                    "id": "Id, ascending. An id never changes, so use this for a stable full export.",
                    "updated_at": "Last-write timestamp; pair with the id tiebreaker for sync."
                }
            },
            "ContactInclude": {
                "name": "include",
                "in": "query",
                "description": "Comma-separated names to inline, at most five. Each is a single name from the list below, never a dotted path: nested includes are not supported. `contactable`: Expands the parent record into a CustomerSummary or a UserSummary. The `user` branch is cross-prefix: expanding it additionally needs `users::read` and a token covering the users prefix, and without them the row keeps the {\"id\": N} reference form with `contactable_type` intact rather than failing. `role`: Expands the assigned contact role into {id, name}.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^(contactable|role)(,(contactable|role)){0,4}$"
                },
                "x-plnnrly-include-paths": {
                    "contactable": {
                        "description": "Expands the parent record into a CustomerSummary or a UserSummary. The `user` branch is cross-prefix: expanding it additionally needs `users::read` and a token covering the users prefix, and without them the row keeps the {\"id\": N} reference form with `contactable_type` intact rather than failing.",
                        "kind": "relation",
                        "to_many": false
                    },
                    "role": {
                        "description": "Expands the assigned contact role into {id, name}.",
                        "kind": "relation",
                        "to_many": false
                    }
                }
            },
            "ContactRoleSort": {
                "name": "sort",
                "in": "query",
                "description": "Up to three keys, comma-separated, each optionally prefixed with `-` for descending. `name`: Role name, A→Z. `id`: Id, ascending. An id never changes, so use this for a stable full export. `updated_at`: Last-write timestamp; pair with the id tiebreaker for sync.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^-?(name|id|updated_at)(,-?(name|id|updated_at)){0,2}$",
                    "default": "name"
                },
                "x-plnnrly-sort-keys": {
                    "name": "Role name, A→Z.",
                    "id": "Id, ascending. An id never changes, so use this for a stable full export.",
                    "updated_at": "Last-write timestamp; pair with the id tiebreaker for sync."
                }
            },
            "ContactSort": {
                "name": "sort",
                "in": "query",
                "description": "Up to three keys, comma-separated, each optionally prefixed with `-` for descending. `name`: Contact name, A→Z. `id`: Id, ascending. An id never changes, so use this for a stable full export. `updated_at`: Last-write timestamp; pair with the id tiebreaker for sync.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^-?(name|id|updated_at)(,-?(name|id|updated_at)){0,2}$",
                    "default": "name"
                },
                "x-plnnrly-sort-keys": {
                    "name": "Contact name, A→Z.",
                    "id": "Id, ascending. An id never changes, so use this for a stable full export.",
                    "updated_at": "Last-write timestamp; pair with the id tiebreaker for sync."
                }
            },
            "Cursor": {
                "name": "cursor",
                "in": "query",
                "description": "Opaque keyset cursor from a previous page's links.next. Never construct one by hand.",
                "required": false,
                "schema": {
                    "type": "string"
                }
            },
            "CustomerCategorySort": {
                "name": "sort",
                "in": "query",
                "description": "Up to three keys, comma-separated, each optionally prefixed with `-` for descending. `name`: Category name, A→Z. `id`: Id, ascending. An id never changes, so use this for a stable full export. `updated_at`: Last-write timestamp; pair with the id tiebreaker for sync.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^-?(name|id|updated_at)(,-?(name|id|updated_at)){0,2}$",
                    "default": "name"
                },
                "x-plnnrly-sort-keys": {
                    "name": "Category name, A→Z.",
                    "id": "Id, ascending. An id never changes, so use this for a stable full export.",
                    "updated_at": "Last-write timestamp; pair with the id tiebreaker for sync."
                }
            },
            "CustomerInclude": {
                "name": "include",
                "in": "query",
                "description": "Comma-separated names to inline, at most five. Each is a single name from the list below, never a dotted path: nested includes are not supported. `company_branch`: Expands the industry branch into {id, name}. `customer_category`: Expands the customer category into {id, name}. Cross-prefix: it additionally needs `customer_categories::read` and a token covering that prefix, and without them the row keeps the {\"id\": N} reference form. `tags`: Tags on this customer. A customer takes at most 50, so this is the whole set.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^(company_branch|customer_category|tags)(,(company_branch|customer_category|tags)){0,4}$"
                },
                "x-plnnrly-include-paths": {
                    "company_branch": {
                        "description": "Expands the industry branch into {id, name}.",
                        "kind": "relation",
                        "to_many": false
                    },
                    "customer_category": {
                        "description": "Expands the customer category into {id, name}. Cross-prefix: it additionally needs `customer_categories::read` and a token covering that prefix, and without them the row keeps the {\"id\": N} reference form.",
                        "kind": "relation",
                        "to_many": false
                    },
                    "tags": {
                        "description": "Tags on this customer. A customer takes at most 50, so this is the whole set.",
                        "kind": "relation",
                        "to_many": true
                    }
                }
            },
            "CustomerSort": {
                "name": "sort",
                "in": "query",
                "description": "Up to three keys, comma-separated, each optionally prefixed with `-` for descending. `name`: Customer name, A→Z. `founded_at`: Day the customer's company was founded. `customer_since`: Day this customer became a customer. `exact_synced_at`: When the Exact sync last handled this customer. `id`: Id, ascending. An id never changes, so use this for a stable full export. `updated_at`: Last-write timestamp; pair with the id tiebreaker for sync.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^-?(name|founded_at|customer_since|exact_synced_at|id|updated_at)(,-?(name|founded_at|customer_since|exact_synced_at|id|updated_at)){0,2}$",
                    "default": "name"
                },
                "x-plnnrly-sort-keys": {
                    "name": "Customer name, A→Z.",
                    "founded_at": "Day the customer's company was founded.",
                    "customer_since": "Day this customer became a customer.",
                    "exact_synced_at": "When the Exact sync last handled this customer.",
                    "id": "Id, ascending. An id never changes, so use this for a stable full export.",
                    "updated_at": "Last-write timestamp; pair with the id tiebreaker for sync."
                }
            },
            "CustomerSubscriptionInclude": {
                "name": "include",
                "in": "query",
                "description": "Comma-separated names to inline, at most five. Each is a single name from the list below, never a dotted path: nested includes are not supported. `customer`: The customer this subscription belongs to, with their name and code. Requires the `customers::read` permission and a token scope covering the customers prefix. `subscription_type`: The subscription type this subscription is an instance of, with its name, which is the name plnnrly shows for the subscription. Requires the `subscription_types::read` permission and a token scope covering the `subscription_types` prefix.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^(customer|subscription_type)(,(customer|subscription_type)){0,4}$"
                },
                "x-plnnrly-include-paths": {
                    "customer": {
                        "description": "The customer this subscription belongs to, with their name and code. Requires the `customers::read` permission and a token scope covering the customers prefix.",
                        "kind": "relation",
                        "to_many": false
                    },
                    "subscription_type": {
                        "description": "The subscription type this subscription is an instance of, with its name, which is the name plnnrly shows for the subscription. Requires the `subscription_types::read` permission and a token scope covering the `subscription_types` prefix.",
                        "kind": "relation",
                        "to_many": false
                    }
                }
            },
            "CustomerSubscriptionSort": {
                "name": "sort",
                "in": "query",
                "description": "Up to three keys, comma-separated, each optionally prefixed with `-` for descending. `starts_at`: First day the subscription runs from, newest first. `next_invoice_date`: First day of the period to be invoiced next, earliest first. `cancelled_at`: Cancellation date, earliest first. `expires_at`: Expiry date, earliest first. `last_invoiced_at`: Date the subscription was last invoiced, earliest first. `id`: Id, ascending. An id never changes, so use this for a stable full export. `updated_at`: Last-write timestamp; pair with the id tiebreaker for sync.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^-?(starts_at|next_invoice_date|cancelled_at|expires_at|last_invoiced_at|id|updated_at)(,-?(starts_at|next_invoice_date|cancelled_at|expires_at|last_invoiced_at|id|updated_at)){0,2}$",
                    "default": "starts_at"
                },
                "x-plnnrly-sort-keys": {
                    "starts_at": "First day the subscription runs from, newest first.",
                    "next_invoice_date": "First day of the period to be invoiced next, earliest first.",
                    "cancelled_at": "Cancellation date, earliest first.",
                    "expires_at": "Expiry date, earliest first.",
                    "last_invoiced_at": "Date the subscription was last invoiced, earliest first.",
                    "id": "Id, ascending. An id never changes, so use this for a stable full export.",
                    "updated_at": "Last-write timestamp; pair with the id tiebreaker for sync."
                }
            },
            "DriveDocumentInclude": {
                "name": "include",
                "in": "query",
                "description": "Comma-separated names to inline, at most five. Each is a single name from the list below, never a dotted path: nested includes are not supported. `content_html`: The document body as sanitised HTML instead of Markdown. Ask for it when the document may contain colours, highlights, alignment or @mentions, which the Markdown `content` field cannot represent.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^(content_html)(,(content_html)){0,4}$"
                },
                "x-plnnrly-include-paths": {
                    "content_html": {
                        "description": "The document body as sanitised HTML instead of Markdown. Ask for it when the document may contain colours, highlights, alignment or @mentions, which the Markdown `content` field cannot represent.",
                        "cost": "renders the HTML for each row",
                        "kind": "field",
                        "to_many": false
                    }
                }
            },
            "DriveDocumentSort": {
                "name": "sort",
                "in": "query",
                "description": "Up to three keys, comma-separated, each optionally prefixed with `-` for descending. `id`: Id, ascending. An id never changes, so use this for a stable full export. `updated_at`: Last-write timestamp; pair with the id tiebreaker for sync.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^-?(id|updated_at)(,-?(id|updated_at)){0,2}$",
                    "default": "-updated_at"
                },
                "x-plnnrly-sort-keys": {
                    "id": "Id, ascending. An id never changes, so use this for a stable full export.",
                    "updated_at": "Last-write timestamp; pair with the id tiebreaker for sync."
                }
            },
            "DriveFolderInclude": {
                "name": "include",
                "in": "query",
                "description": "Comma-separated names to inline, at most five. Each is a single name from the list below, never a dotted path: nested includes are not supported. `owner`: The project, customer, organisation or user whose drive this folder lives in. `parent_folder`: The folder this one sits in; absent for folders at the drive root.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^(owner|parent_folder)(,(owner|parent_folder)){0,4}$"
                },
                "x-plnnrly-include-paths": {
                    "owner": {
                        "description": "The project, customer, organisation or user whose drive this folder lives in.",
                        "cost": "one extra lookup per record type on the page",
                        "kind": "relation",
                        "to_many": false
                    },
                    "parent_folder": {
                        "description": "The folder this one sits in; absent for folders at the drive root.",
                        "cost": "one query per page",
                        "kind": "relation",
                        "to_many": false
                    }
                }
            },
            "DriveFolderSort": {
                "name": "sort",
                "in": "query",
                "description": "Up to three keys, comma-separated, each optionally prefixed with `-` for descending. `name`: Folder name, A to Z. `order`: Sort position within the drive and level, as shown in the drive tree. Positions count per drive and level, so this order is only meaningful once the collection is narrowed with `filter[owner_type]`, `filter[owner_id]` and `filter[parent_id]`; across the whole organisation it interleaves every drive and every level. This position changes whenever someone drags the list into a new order in plnnrly, so paging by number over it can skip or repeat a row. `id`: Id, ascending. An id never changes, so use this for a stable full export. `updated_at`: Last-write timestamp; pair with the id tiebreaker for sync.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^-?(name|order|id|updated_at)(,-?(name|order|id|updated_at)){0,2}$",
                    "default": "name"
                },
                "x-plnnrly-sort-keys": {
                    "name": "Folder name, A to Z.",
                    "order": "Sort position within the drive and level, as shown in the drive tree. Positions count per drive and level, so this order is only meaningful once the collection is narrowed with `filter[owner_type]`, `filter[owner_id]` and `filter[parent_id]`; across the whole organisation it interleaves every drive and every level.",
                    "id": "Id, ascending. An id never changes, so use this for a stable full export.",
                    "updated_at": "Last-write timestamp; pair with the id tiebreaker for sync."
                }
            },
            "DriveItemInclude": {
                "name": "include",
                "in": "query",
                "description": "Comma-separated names to inline, at most five. Each is a single name from the list below, never a dotted path: nested includes are not supported. `drive_folder`: The folder this item sits in; absent for items at the drive root. `owner`: The project, customer, organisation or user whose drive this item lives in. `tags`: Tags on this item. An item takes at most 50, so this is the whole set.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^(drive_folder|owner|tags)(,(drive_folder|owner|tags)){0,4}$"
                },
                "x-plnnrly-include-paths": {
                    "drive_folder": {
                        "description": "The folder this item sits in; absent for items at the drive root.",
                        "cost": "one query per page",
                        "kind": "relation",
                        "to_many": false
                    },
                    "owner": {
                        "description": "The project, customer, organisation or user whose drive this item lives in.",
                        "cost": "one extra lookup per record type on the page",
                        "kind": "relation",
                        "to_many": false
                    },
                    "tags": {
                        "description": "Tags on this item. An item takes at most 50, so this is the whole set.",
                        "cost": "one query per page",
                        "kind": "relation",
                        "to_many": true
                    }
                }
            },
            "DriveItemSort": {
                "name": "sort",
                "in": "query",
                "description": "Up to three keys, comma-separated, each optionally prefixed with `-` for descending. `order_column`: Sort position within the owner and folder, as shown in the drive tree. Positions count per drive and folder, so this order is only meaningful once the collection is narrowed with `filter[owner_type]`, `filter[owner_id]` and `filter[folder_id]`; across the whole organisation it interleaves every drive. This position changes whenever someone drags the list into a new order in plnnrly, so paging by number over it can skip or repeat a row. `title`: Item title, A to Z. `id`: Id, ascending. An id never changes, so use this for a stable full export. `updated_at`: Last-write timestamp; pair with the id tiebreaker for sync.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^-?(order_column|title|id|updated_at)(,-?(order_column|title|id|updated_at)){0,2}$",
                    "default": "order_column"
                },
                "x-plnnrly-sort-keys": {
                    "order_column": "Sort position within the owner and folder, as shown in the drive tree. Positions count per drive and folder, so this order is only meaningful once the collection is narrowed with `filter[owner_type]`, `filter[owner_id]` and `filter[folder_id]`; across the whole organisation it interleaves every drive.",
                    "title": "Item title, A to Z.",
                    "id": "Id, ascending. An id never changes, so use this for a stable full export.",
                    "updated_at": "Last-write timestamp; pair with the id tiebreaker for sync."
                }
            },
            "EventInclude": {
                "name": "include",
                "in": "query",
                "description": "Comma-separated names to inline, at most five. Each is a single name from the list below, never a dotted path: nested includes are not supported. `user`: The user whose agenda this event is on.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^(user)(,(user)){0,4}$"
                },
                "x-plnnrly-include-paths": {
                    "user": {
                        "description": "The user whose agenda this event is on.",
                        "kind": "relation",
                        "to_many": false
                    }
                }
            },
            "EventSort": {
                "name": "sort",
                "in": "query",
                "description": "Up to three keys, comma-separated, each optionally prefixed with `-` for descending. `start`: When the event starts, latest first. `end`: When the event ends. `title`: Event title, alphabetically. `occurrence_date`: The series date a separated occurrence replaces. `recurrence_start`: First moment a series may generate an occurrence. `recurrence_end`: Last moment a series may generate an occurrence. `recurrence_frequency`: The unit a series repeats in, in the order day, week, month, year. `id`: Id, ascending. An id never changes, so use this for a stable full export. `updated_at`: Last-write timestamp; pair with the id tiebreaker for sync.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^-?(start|end|title|occurrence_date|recurrence_start|recurrence_end|recurrence_frequency|id|updated_at)(,-?(start|end|title|occurrence_date|recurrence_start|recurrence_end|recurrence_frequency|id|updated_at)){0,2}$",
                    "default": "start"
                },
                "x-plnnrly-sort-keys": {
                    "start": "When the event starts, latest first.",
                    "end": "When the event ends.",
                    "title": "Event title, alphabetically.",
                    "occurrence_date": "The series date a separated occurrence replaces.",
                    "recurrence_start": "First moment a series may generate an occurrence.",
                    "recurrence_end": "Last moment a series may generate an occurrence.",
                    "recurrence_frequency": "The unit a series repeats in, in the order day, week, month, year.",
                    "id": "Id, ascending. An id never changes, so use this for a stable full export.",
                    "updated_at": "Last-write timestamp; pair with the id tiebreaker for sync."
                }
            },
            "Force": {
                "name": "force",
                "in": "query",
                "description": "Delete permanently instead of soft-deleting, so the record cannot be restored. It needs no token ability beyond the one every delete needs.",
                "required": false,
                "schema": {
                    "type": "boolean",
                    "default": false
                }
            },
            "FreeDayInclude": {
                "name": "include",
                "in": "query",
                "description": "Comma-separated names to inline, at most five. Each is a single name from the list below, never a dotted path: nested includes are not supported. `holiday`: The recurring holiday this free day instantiates; absent for a manually added day.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^(holiday)(,(holiday)){0,4}$"
                },
                "x-plnnrly-include-paths": {
                    "holiday": {
                        "description": "The recurring holiday this free day instantiates; absent for a manually added day.",
                        "kind": "relation",
                        "to_many": false
                    }
                }
            },
            "FreeDaySort": {
                "name": "sort",
                "in": "query",
                "description": "Up to three keys, comma-separated, each optionally prefixed with `-` for descending. `date`: Calendar date the organisation is closed, newest first. `id`: Id, ascending. An id never changes, so use this for a stable full export. `updated_at`: Last-write timestamp; pair with the id tiebreaker for sync.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^-?(date|id|updated_at)(,-?(date|id|updated_at)){0,2}$",
                    "default": "date"
                },
                "x-plnnrly-sort-keys": {
                    "date": "Calendar date the organisation is closed, newest first.",
                    "id": "Id, ascending. An id never changes, so use this for a stable full export.",
                    "updated_at": "Last-write timestamp; pair with the id tiebreaker for sync."
                }
            },
            "FurloughBalanceMutationInclude": {
                "name": "include",
                "in": "query",
                "description": "Comma-separated names to inline, at most five. Each is a single name from the list below, never a dotted path: nested includes are not supported. `user`: The employee this mutation belongs to.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^(user)(,(user)){0,4}$"
                },
                "x-plnnrly-include-paths": {
                    "user": {
                        "description": "The employee this mutation belongs to.",
                        "kind": "relation",
                        "to_many": false
                    }
                }
            },
            "FurloughBalanceMutationSort": {
                "name": "sort",
                "in": "query",
                "description": "Up to three keys, comma-separated, each optionally prefixed with `-` for descending. `created_at`: When the mutation was filed, newest first. `start`: Start of the period the mutation covers. `end`: End of the period the mutation covers. `year`: The balance year the mutation counts against. `id`: Id, ascending. An id never changes, so use this for a stable full export. `updated_at`: Last-write timestamp; pair with the id tiebreaker for sync.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^-?(created_at|start|end|year|id|updated_at)(,-?(created_at|start|end|year|id|updated_at)){0,2}$",
                    "default": "created_at"
                },
                "x-plnnrly-sort-keys": {
                    "created_at": "When the mutation was filed, newest first.",
                    "start": "Start of the period the mutation covers.",
                    "end": "End of the period the mutation covers.",
                    "year": "The balance year the mutation counts against.",
                    "id": "Id, ascending. An id never changes, so use this for a stable full export.",
                    "updated_at": "Last-write timestamp; pair with the id tiebreaker for sync."
                }
            },
            "HolidayPageNumber": {
                "name": "page",
                "in": "query",
                "description": "Opt in to page-number paging: 1-based, at most 1000. Omit it, the default, to page by opaque cursor, which is what links.next hands you. Cannot be combined with cursor. Published only on collections that count their rows on every request. This collection's default sort is a display position that changes whenever someone drags the list into a new order in plnnrly, so a page number can skip or repeat a row if the list is reordered between two page requests. These collections rarely exceed one page; for a stable walk, send sort=id and follow links.next without page.",
                "required": false,
                "schema": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 1000
                },
                "x-plnnrly-constraint": "This collection's default sort is a display position that changes whenever someone drags the list into a new order in plnnrly, so a page number can skip or repeat a row if the list is reordered between two page requests. These collections rarely exceed one page; for a stable walk, send sort=id and follow links.next without page."
            },
            "HolidaySort": {
                "name": "sort",
                "in": "query",
                "description": "Up to three keys, comma-separated, each optionally prefixed with `-` for descending. `sort_order`: Position in the organisation's holiday list, as ordered in plnnrly. This position changes whenever someone drags the list into a new order in plnnrly, so paging by number over it can skip or repeat a row. `name`: Holiday name, A→Z. `id`: Id, ascending. An id never changes, so use this for a stable full export. `updated_at`: Last-write timestamp; pair with the id tiebreaker for sync.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^-?(sort_order|name|id|updated_at)(,-?(sort_order|name|id|updated_at)){0,2}$",
                    "default": "sort_order"
                },
                "x-plnnrly-sort-keys": {
                    "sort_order": "Position in the organisation's holiday list, as ordered in plnnrly.",
                    "name": "Holiday name, A→Z.",
                    "id": "Id, ascending. An id never changes, so use this for a stable full export.",
                    "updated_at": "Last-write timestamp; pair with the id tiebreaker for sync."
                }
            },
            "IdempotencyKey": {
                "name": "Idempotency-Key",
                "in": "header",
                "description": "A caller-chosen key that makes this POST safe to retry. Replaying it returns the original response with Idempotency-Replayed: true.",
                "required": false,
                "schema": {
                    "type": "string",
                    "maxLength": 255
                }
            },
            "IdempotencyKeyRequired": {
                "name": "Idempotency-Key",
                "in": "header",
                "description": "A caller-chosen key that makes this POST safe to retry. Required on this operation: omitting it is 400 idempotency-key-required.",
                "required": true,
                "schema": {
                    "type": "string",
                    "maxLength": 255
                }
            },
            "IfMatch": {
                "name": "If-Match",
                "in": "header",
                "description": "The ETag a write is conditional on. A stale validator is 412 precondition-failed.",
                "required": false,
                "schema": {
                    "type": "string"
                }
            },
            "IfNoneMatch": {
                "name": "If-None-Match",
                "in": "header",
                "description": "The ETag a read is conditional on. A match is answered 304 with no body.",
                "required": false,
                "schema": {
                    "type": "string"
                }
            },
            "InvoiceInclude": {
                "name": "include",
                "in": "query",
                "description": "Comma-separated names to inline, at most five. Each is a single name from the list below, never a dotted path: nested includes are not supported. `project`: The project this invoice was raised for, with its title and customer. Requires the `projects::read` permission and a token scope covering the projects prefix. `user`: The employee this invoice is attributed to, with their name. Requires the `users::read` permission and a token scope covering the users prefix.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^(project|user)(,(project|user)){0,4}$"
                },
                "x-plnnrly-include-paths": {
                    "project": {
                        "description": "The project this invoice was raised for, with its title and customer. Requires the `projects::read` permission and a token scope covering the projects prefix.",
                        "kind": "relation",
                        "to_many": false
                    },
                    "user": {
                        "description": "The employee this invoice is attributed to, with their name. Requires the `users::read` permission and a token scope covering the users prefix.",
                        "kind": "relation",
                        "to_many": false
                    }
                }
            },
            "InvoiceSort": {
                "name": "sort",
                "in": "query",
                "description": "Up to three keys, comma-separated, each optionally prefixed with `-` for descending. `date`: Invoice date, newest first. `id`: Id, ascending. An id never changes, so use this for a stable full export. `updated_at`: Last-write timestamp; pair with the id tiebreaker for sync.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^-?(date|id|updated_at)(,-?(date|id|updated_at)){0,2}$",
                    "default": "date"
                },
                "x-plnnrly-sort-keys": {
                    "date": "Invoice date, newest first.",
                    "id": "Id, ascending. An id never changes, so use this for a stable full export.",
                    "updated_at": "Last-write timestamp; pair with the id tiebreaker for sync."
                }
            },
            "LabelPageNumber": {
                "name": "page",
                "in": "query",
                "description": "Opt in to page-number paging: 1-based, at most 1000. Omit it, the default, to page by opaque cursor, which is what links.next hands you. Cannot be combined with cursor. Published only on collections that count their rows on every request. This collection's default sort is a display position that changes whenever someone drags the list into a new order in plnnrly, so a page number can skip or repeat a row if the list is reordered between two page requests. These collections rarely exceed one page; for a stable walk, send sort=id and follow links.next without page.",
                "required": false,
                "schema": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 1000
                },
                "x-plnnrly-constraint": "This collection's default sort is a display position that changes whenever someone drags the list into a new order in plnnrly, so a page number can skip or repeat a row if the list is reordered between two page requests. These collections rarely exceed one page; for a stable walk, send sort=id and follow links.next without page."
            },
            "LabelSort": {
                "name": "sort",
                "in": "query",
                "description": "Up to three keys, comma-separated, each optionally prefixed with `-` for descending. `order`: Display position within the project, as ordered in plnnrly. This position changes whenever someone drags the list into a new order in plnnrly, so paging by number over it can skip or repeat a row. `name`: Label name, A→Z. `id`: Id, ascending. An id never changes, so use this for a stable full export. `updated_at`: Last-write timestamp; pair with the id tiebreaker for sync.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^-?(order|name|id|updated_at)(,-?(order|name|id|updated_at)){0,2}$",
                    "default": "order"
                },
                "x-plnnrly-sort-keys": {
                    "order": "Display position within the project, as ordered in plnnrly.",
                    "name": "Label name, A→Z.",
                    "id": "Id, ascending. An id never changes, so use this for a stable full export.",
                    "updated_at": "Last-write timestamp; pair with the id tiebreaker for sync."
                }
            },
            "MediaInclude": {
                "name": "include",
                "in": "query",
                "description": "Comma-separated names to inline, at most five. Each is a single name from the list below, never a dotted path: nested includes are not supported. `url`: Signed download URL. Null where your organisation's file storage cannot sign URLs, with a `signed_url_unavailable` warning. `url_expires_at`: When the signed `url` stops working. Always `null` in v2.0: the signed URL does not expire. Request together with url.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^(url|url_expires_at)(,(url|url_expires_at)){0,4}$"
                },
                "x-plnnrly-include-paths": {
                    "url": {
                        "description": "Signed download URL. Null where your organisation's file storage cannot sign URLs, with a `signed_url_unavailable` warning.",
                        "cost": "one signing call per row",
                        "kind": "field",
                        "to_many": false
                    },
                    "url_expires_at": {
                        "description": "When the signed `url` stops working. Always `null` in v2.0: the signed URL does not expire. Request together with url.",
                        "cost": "free once url is requested",
                        "kind": "field",
                        "to_many": false
                    }
                }
            },
            "MediaSort": {
                "name": "sort",
                "in": "query",
                "description": "Up to three keys, comma-separated, each optionally prefixed with `-` for descending. `created_at`: Upload timestamp, newest first. `name`: File name, A→Z. `id`: Id, ascending. An id never changes, so use this for a stable full export. `updated_at`: Last-write timestamp; pair with the id tiebreaker for sync.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^-?(created_at|name|id|updated_at)(,-?(created_at|name|id|updated_at)){0,2}$",
                    "default": "created_at"
                },
                "x-plnnrly-sort-keys": {
                    "created_at": "Upload timestamp, newest first.",
                    "name": "File name, A→Z.",
                    "id": "Id, ascending. An id never changes, so use this for a stable full export.",
                    "updated_at": "Last-write timestamp; pair with the id tiebreaker for sync."
                }
            },
            "Meta": {
                "name": "meta",
                "in": "query",
                "description": "Optional meta members to compute for this response.",
                "required": false,
                "schema": {
                    "type": "array",
                    "items": {
                        "type": "string",
                        "enum": [
                            "actions"
                        ]
                    },
                    "x-extensible-enum": true
                },
                "style": "form",
                "explode": false
            },
            "NoteInclude": {
                "name": "include",
                "in": "query",
                "description": "Comma-separated names to inline, at most five. Each is a single name from the list below, never a dotted path: nested includes are not supported. `notable`: The project, customer or work item the note is attached to. `user`: The user who wrote the note.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^(notable|user)(,(notable|user)){0,4}$"
                },
                "x-plnnrly-include-paths": {
                    "notable": {
                        "description": "The project, customer or work item the note is attached to.",
                        "cost": "one extra lookup per record type on the page",
                        "kind": "relation",
                        "to_many": false
                    },
                    "user": {
                        "description": "The user who wrote the note.",
                        "cost": "one query per page",
                        "kind": "relation",
                        "to_many": false
                    }
                }
            },
            "NoteSort": {
                "name": "sort",
                "in": "query",
                "description": "Up to three keys, comma-separated, each optionally prefixed with `-` for descending. `order_column`: Sort position within the parent record, ascending. This position changes whenever someone drags the list into a new order in plnnrly, so paging by number over it can skip or repeat a row. `id`: Id, ascending. An id never changes, so use this for a stable full export. `updated_at`: Last-write timestamp; pair with the id tiebreaker for sync.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^-?(order_column|id|updated_at)(,-?(order_column|id|updated_at)){0,2}$",
                    "default": "order_column"
                },
                "x-plnnrly-sort-keys": {
                    "order_column": "Sort position within the parent record, ascending.",
                    "id": "Id, ascending. An id never changes, so use this for a stable full export.",
                    "updated_at": "Last-write timestamp; pair with the id tiebreaker for sync."
                }
            },
            "PageNumber": {
                "name": "page",
                "in": "query",
                "description": "Opt in to page-number paging: 1-based, at most 1000. Omit it, the default, to page by opaque cursor, which is what links.next hands you. Cannot be combined with cursor. Published only on collections that count their rows on every request.",
                "required": false,
                "schema": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 1000
                }
            },
            "PerPage": {
                "name": "per_page",
                "in": "query",
                "description": "Rows per page. At most 25 when `include` names a to-many relation; a larger value is 400 invalid-query-parameter.",
                "required": false,
                "schema": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 100,
                    "default": 25
                }
            },
            "ProjectInclude": {
                "name": "include",
                "in": "query",
                "description": "Comma-separated names to inline, at most five. Each is a single name from the list below, never a dotted path: nested includes are not supported. `logged_minutes`: Sum of the worklog minutes on this project that the caller may see. Null without `hours::read` and a token covering `api:read:times`. `tags`: Tags on this project. A project takes at most 50, so this is the whole set.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^(logged_minutes|tags)(,(logged_minutes|tags)){0,4}$"
                },
                "x-plnnrly-include-paths": {
                    "logged_minutes": {
                        "description": "Sum of the worklog minutes on this project that the caller may see. Null without `hours::read` and a token covering `api:read:times`.",
                        "cost": "one grouped aggregate query per page",
                        "kind": "field",
                        "to_many": false
                    },
                    "tags": {
                        "description": "Tags on this project. A project takes at most 50, so this is the whole set.",
                        "kind": "relation",
                        "to_many": true
                    }
                }
            },
            "ProjectLinkInclude": {
                "name": "include",
                "in": "query",
                "description": "Comma-separated names to inline, at most five. Each is a single name from the list below, never a dotted path: nested includes are not supported. `project`: The project this link belongs to, with its title and customer. A link whose project has been soft-deleted stays readable and falls back to the reference form here.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^(project)(,(project)){0,4}$"
                },
                "x-plnnrly-include-paths": {
                    "project": {
                        "description": "The project this link belongs to, with its title and customer. A link whose project has been soft-deleted stays readable and falls back to the reference form here.",
                        "kind": "relation",
                        "to_many": false
                    }
                }
            },
            "ProjectLinkSort": {
                "name": "sort",
                "in": "query",
                "description": "Up to three keys, comma-separated, each optionally prefixed with `-` for descending. `name`: Link name, A→Z. `id`: Id, ascending. An id never changes, so use this for a stable full export. `updated_at`: Last-write timestamp; pair with the id tiebreaker for sync.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^-?(name|id|updated_at)(,-?(name|id|updated_at)){0,2}$",
                    "default": "name"
                },
                "x-plnnrly-sort-keys": {
                    "name": "Link name, A→Z.",
                    "id": "Id, ascending. An id never changes, so use this for a stable full export.",
                    "updated_at": "Last-write timestamp; pair with the id tiebreaker for sync."
                }
            },
            "ProjectSort": {
                "name": "sort",
                "in": "query",
                "description": "Up to three keys, comma-separated, each optionally prefixed with `-` for descending. `title`: Project title, A→Z. `status`: Lifecycle status. `start_date`: First day of the project. `end_date`: Last day of the project. `last_activity`: Most recent write anywhere in the project. `id`: Id, ascending. An id never changes, so use this for a stable full export. `updated_at`: Last-write timestamp; pair with the id tiebreaker for sync.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^-?(title|status|start_date|end_date|last_activity|id|updated_at)(,-?(title|status|start_date|end_date|last_activity|id|updated_at)){0,2}$",
                    "default": "title"
                },
                "x-plnnrly-sort-keys": {
                    "title": "Project title, A→Z.",
                    "status": "Lifecycle status.",
                    "start_date": "First day of the project.",
                    "end_date": "Last day of the project.",
                    "last_activity": "Most recent write anywhere in the project.",
                    "id": "Id, ascending. An id never changes, so use this for a stable full export.",
                    "updated_at": "Last-write timestamp; pair with the id tiebreaker for sync."
                }
            },
            "SalesInvoiceInclude": {
                "name": "include",
                "in": "query",
                "description": "Comma-separated names to inline, at most five. Each is a single name from the list below, never a dotted path: nested includes are not supported. `customer`: The customer this invoice is billed to, with their name and code. Requires the `customers::read` permission and a token scope covering the customers prefix.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^(customer)(,(customer)){0,4}$"
                },
                "x-plnnrly-include-paths": {
                    "customer": {
                        "description": "The customer this invoice is billed to, with their name and code. Requires the `customers::read` permission and a token scope covering the customers prefix.",
                        "kind": "relation",
                        "to_many": false
                    }
                }
            },
            "SalesInvoiceRowInclude": {
                "name": "include",
                "in": "query",
                "description": "Comma-separated names to inline, at most five. Each is a single name from the list below, never a dotted path: nested includes are not supported. `sales_invoice`: The invoice this line belongs to.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^(sales_invoice)(,(sales_invoice)){0,4}$"
                },
                "x-plnnrly-include-paths": {
                    "sales_invoice": {
                        "description": "The invoice this line belongs to.",
                        "kind": "relation",
                        "to_many": false
                    }
                }
            },
            "SalesInvoiceRowPageNumber": {
                "name": "page",
                "in": "query",
                "description": "Opt in to page-number paging: 1-based, at most 1000. Omit it, the default, to page by opaque cursor, which is what links.next hands you. Cannot be combined with cursor. Published only on collections that count their rows on every request. This collection's default sort is a display position that changes whenever someone drags the list into a new order in plnnrly, so a page number can skip or repeat a row if the list is reordered between two page requests. These collections rarely exceed one page; for a stable walk, send sort=id and follow links.next without page.",
                "required": false,
                "schema": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 1000
                },
                "x-plnnrly-constraint": "This collection's default sort is a display position that changes whenever someone drags the list into a new order in plnnrly, so a page number can skip or repeat a row if the list is reordered between two page requests. These collections rarely exceed one page; for a stable walk, send sort=id and follow links.next without page."
            },
            "SalesInvoiceRowSort": {
                "name": "sort",
                "in": "query",
                "description": "Up to three keys, comma-separated, each optionally prefixed with `-` for descending. `sort_order`: Position of the line within its invoice, first to last. This position changes whenever someone drags the list into a new order in plnnrly, so paging by number over it can skip or repeat a row. `name`: Line label, A→Z. `id`: Id, ascending. An id never changes, so use this for a stable full export. `updated_at`: Last-write timestamp; pair with the id tiebreaker for sync.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^-?(sort_order|name|id|updated_at)(,-?(sort_order|name|id|updated_at)){0,2}$",
                    "default": "sort_order"
                },
                "x-plnnrly-sort-keys": {
                    "sort_order": "Position of the line within its invoice, first to last.",
                    "name": "Line label, A→Z.",
                    "id": "Id, ascending. An id never changes, so use this for a stable full export.",
                    "updated_at": "Last-write timestamp; pair with the id tiebreaker for sync."
                }
            },
            "SalesInvoiceSort": {
                "name": "sort",
                "in": "query",
                "description": "Up to three keys, comma-separated, each optionally prefixed with `-` for descending. `invoice_date`: Invoice date, newest first by default. `due_date`: Date payment is due. `synced_at`: When the invoice last synced successfully. `sync_attempted_at`: When the invoice was last handed to the sync. `paid_at`: When the invoice was settled. `id`: Id, ascending. An id never changes, so use this for a stable full export. `updated_at`: Last-write timestamp; pair with the id tiebreaker for sync.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^-?(invoice_date|due_date|synced_at|sync_attempted_at|paid_at|id|updated_at)(,-?(invoice_date|due_date|synced_at|sync_attempted_at|paid_at|id|updated_at)){0,2}$",
                    "default": "invoice_date"
                },
                "x-plnnrly-sort-keys": {
                    "invoice_date": "Invoice date, newest first by default.",
                    "due_date": "Date payment is due.",
                    "synced_at": "When the invoice last synced successfully.",
                    "sync_attempted_at": "When the invoice was last handed to the sync.",
                    "paid_at": "When the invoice was settled.",
                    "id": "Id, ascending. An id never changes, so use this for a stable full export.",
                    "updated_at": "Last-write timestamp; pair with the id tiebreaker for sync."
                }
            },
            "SprintInclude": {
                "name": "include",
                "in": "query",
                "description": "Comma-separated names to inline, at most five. Each is a single name from the list below, never a dotted path: nested includes are not supported. `project`: The project this sprint belongs to. Requires the projects prefix's read grant and a token scope covering it; without both it renders as a bare id reference.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^(project)(,(project)){0,4}$"
                },
                "x-plnnrly-include-paths": {
                    "project": {
                        "description": "The project this sprint belongs to. Requires the projects prefix's read grant and a token scope covering it; without both it renders as a bare id reference.",
                        "kind": "relation",
                        "to_many": false
                    }
                }
            },
            "SprintPageNumber": {
                "name": "page",
                "in": "query",
                "description": "Opt in to page-number paging: 1-based, at most 1000. Omit it, the default, to page by opaque cursor, which is what links.next hands you. Cannot be combined with cursor. Published only on collections that count their rows on every request. This collection's default sort is a display position that changes whenever someone drags the list into a new order in plnnrly, so a page number can skip or repeat a row if the list is reordered between two page requests. These collections rarely exceed one page; for a stable walk, send sort=id and follow links.next without page.",
                "required": false,
                "schema": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 1000
                },
                "x-plnnrly-constraint": "This collection's default sort is a display position that changes whenever someone drags the list into a new order in plnnrly, so a page number can skip or repeat a row if the list is reordered between two page requests. These collections rarely exceed one page; for a stable walk, send sort=id and follow links.next without page."
            },
            "SprintSort": {
                "name": "sort",
                "in": "query",
                "description": "Up to three keys, comma-separated, each optionally prefixed with `-` for descending. `order`: Display position within the project, as ordered in plnnrly. The default sort; the id breaks ties, because positions are unique within a project only. This position changes whenever someone drags the list into a new order in plnnrly, so paging by number over it can skip or repeat a row. `name`: Sprint name, A→Z. `start_date`: Planned first day of the sprint. `end_date`: Planned last day of the sprint. `started_at`: When the sprint was actually started. `completed_at`: When the sprint was actually completed. Newest first, with `-`, is the sprint-history order. `id`: Id, ascending. An id never changes, so use this for a stable full export. `updated_at`: Last-write timestamp; pair with the id tiebreaker for sync.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^-?(order|name|start_date|end_date|started_at|completed_at|id|updated_at)(,-?(order|name|start_date|end_date|started_at|completed_at|id|updated_at)){0,2}$",
                    "default": "order"
                },
                "x-plnnrly-sort-keys": {
                    "order": "Display position within the project, as ordered in plnnrly. The default sort; the id breaks ties, because positions are unique within a project only.",
                    "name": "Sprint name, A→Z.",
                    "start_date": "Planned first day of the sprint.",
                    "end_date": "Planned last day of the sprint.",
                    "started_at": "When the sprint was actually started.",
                    "completed_at": "When the sprint was actually completed. Newest first, with `-`, is the sprint-history order.",
                    "id": "Id, ascending. An id never changes, so use this for a stable full export.",
                    "updated_at": "Last-write timestamp; pair with the id tiebreaker for sync."
                }
            },
            "SubscriptionItemInclude": {
                "name": "include",
                "in": "query",
                "description": "Comma-separated names to inline, at most five. Each is a single name from the list below, never a dotted path: nested includes are not supported. `customer_subscription`: The subscription this line belongs to. `subscribable`: The subscription product this line charges for.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^(customer_subscription|subscribable)(,(customer_subscription|subscribable)){0,4}$"
                },
                "x-plnnrly-include-paths": {
                    "customer_subscription": {
                        "description": "The subscription this line belongs to.",
                        "cost": "one query per page",
                        "kind": "relation",
                        "to_many": false
                    },
                    "subscribable": {
                        "description": "The subscription product this line charges for.",
                        "cost": "one extra lookup per record type on the page",
                        "kind": "relation",
                        "to_many": false
                    }
                }
            },
            "SubscriptionItemPageNumber": {
                "name": "page",
                "in": "query",
                "description": "Opt in to page-number paging: 1-based, at most 1000. Omit it, the default, to page by opaque cursor, which is what links.next hands you. Cannot be combined with cursor. Published only on collections that count their rows on every request. This collection's default sort is a display position that changes whenever someone drags the list into a new order in plnnrly, so a page number can skip or repeat a row if the list is reordered between two page requests. These collections rarely exceed one page; for a stable walk, send sort=id and follow links.next without page.",
                "required": false,
                "schema": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 1000
                },
                "x-plnnrly-constraint": "This collection's default sort is a display position that changes whenever someone drags the list into a new order in plnnrly, so a page number can skip or repeat a row if the list is reordered between two page requests. These collections rarely exceed one page; for a stable walk, send sort=id and follow links.next without page."
            },
            "SubscriptionItemSort": {
                "name": "sort",
                "in": "query",
                "description": "Up to three keys, comma-separated, each optionally prefixed with `-` for descending. `sort_order`: Position of the line within its subscription, first to last. This position changes whenever someone drags the list into a new order in plnnrly, so paging by number over it can skip or repeat a row. `id`: Id, ascending. An id never changes, so use this for a stable full export. `updated_at`: Last-write timestamp; pair with the id tiebreaker for sync.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^-?(sort_order|id|updated_at)(,-?(sort_order|id|updated_at)){0,2}$",
                    "default": "sort_order"
                },
                "x-plnnrly-sort-keys": {
                    "sort_order": "Position of the line within its subscription, first to last.",
                    "id": "Id, ascending. An id never changes, so use this for a stable full export.",
                    "updated_at": "Last-write timestamp; pair with the id tiebreaker for sync."
                }
            },
            "SubscriptionProductSort": {
                "name": "sort",
                "in": "query",
                "description": "Up to three keys, comma-separated, each optionally prefixed with `-` for descending. `name`: Product name, A→Z. `id`: Id, ascending. An id never changes, so use this for a stable full export. `updated_at`: Last-write timestamp; pair with the id tiebreaker for sync.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^-?(name|id|updated_at)(,-?(name|id|updated_at)){0,2}$",
                    "default": "name"
                },
                "x-plnnrly-sort-keys": {
                    "name": "Product name, A→Z.",
                    "id": "Id, ascending. An id never changes, so use this for a stable full export.",
                    "updated_at": "Last-write timestamp; pair with the id tiebreaker for sync."
                }
            },
            "SubscriptionTypeItemInclude": {
                "name": "include",
                "in": "query",
                "description": "Comma-separated names to inline, at most five. Each is a single name from the list below, never a dotted path: nested includes are not supported. `subscribable`: The subscription product this template line sells. Expanding it additionally requires the read permission and token scope of the target's own prefix: `subscription_products` for a product. `subscription_type`: The subscription type this template line belongs to.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^(subscribable|subscription_type)(,(subscribable|subscription_type)){0,4}$"
                },
                "x-plnnrly-include-paths": {
                    "subscribable": {
                        "description": "The subscription product this template line sells. Expanding it additionally requires the read permission and token scope of the target's own prefix: `subscription_products` for a product.",
                        "cost": "one extra lookup per record type on the page",
                        "kind": "relation",
                        "to_many": false
                    },
                    "subscription_type": {
                        "description": "The subscription type this template line belongs to.",
                        "kind": "relation",
                        "to_many": false
                    }
                }
            },
            "SubscriptionTypeItemPageNumber": {
                "name": "page",
                "in": "query",
                "description": "Opt in to page-number paging: 1-based, at most 1000. Omit it, the default, to page by opaque cursor, which is what links.next hands you. Cannot be combined with cursor. Published only on collections that count their rows on every request. This collection's default sort is a display position that changes whenever someone drags the list into a new order in plnnrly, so a page number can skip or repeat a row if the list is reordered between two page requests. These collections rarely exceed one page; for a stable walk, send sort=id and follow links.next without page.",
                "required": false,
                "schema": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 1000
                },
                "x-plnnrly-constraint": "This collection's default sort is a display position that changes whenever someone drags the list into a new order in plnnrly, so a page number can skip or repeat a row if the list is reordered between two page requests. These collections rarely exceed one page; for a stable walk, send sort=id and follow links.next without page."
            },
            "SubscriptionTypeItemSort": {
                "name": "sort",
                "in": "query",
                "description": "Up to three keys, comma-separated, each optionally prefixed with `-` for descending. `sort_order`: Position of the line within its subscription type, first to last. This position changes whenever someone drags the list into a new order in plnnrly, so paging by number over it can skip or repeat a row. `name`: Line label, A→Z. `id`: Id, ascending. An id never changes, so use this for a stable full export. `updated_at`: Last-write timestamp; pair with the id tiebreaker for sync.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^-?(sort_order|name|id|updated_at)(,-?(sort_order|name|id|updated_at)){0,2}$",
                    "default": "sort_order"
                },
                "x-plnnrly-sort-keys": {
                    "sort_order": "Position of the line within its subscription type, first to last.",
                    "name": "Line label, A→Z.",
                    "id": "Id, ascending. An id never changes, so use this for a stable full export.",
                    "updated_at": "Last-write timestamp; pair with the id tiebreaker for sync."
                }
            },
            "SubscriptionTypePageNumber": {
                "name": "page",
                "in": "query",
                "description": "Opt in to page-number paging: 1-based, at most 1000. Omit it, the default, to page by opaque cursor, which is what links.next hands you. Cannot be combined with cursor. Published only on collections that count their rows on every request. This collection's default sort is a display position that changes whenever someone drags the list into a new order in plnnrly, so a page number can skip or repeat a row if the list is reordered between two page requests. These collections rarely exceed one page; for a stable walk, send sort=id and follow links.next without page.",
                "required": false,
                "schema": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 1000
                },
                "x-plnnrly-constraint": "This collection's default sort is a display position that changes whenever someone drags the list into a new order in plnnrly, so a page number can skip or repeat a row if the list is reordered between two page requests. These collections rarely exceed one page; for a stable walk, send sort=id and follow links.next without page."
            },
            "SubscriptionTypeSort": {
                "name": "sort",
                "in": "query",
                "description": "Up to three keys, comma-separated, each optionally prefixed with `-` for descending. `sort_order`: Position in the catalogue, as ordered in plnnrly. This is the default sort. This position changes whenever someone drags the list into a new order in plnnrly, so paging by number over it can skip or repeat a row. `name`: Subscription type name, A→Z. `id`: Id, ascending. An id never changes, so use this for a stable full export. `updated_at`: Last-write timestamp; pair with the id tiebreaker for sync.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^-?(sort_order|name|id|updated_at)(,-?(sort_order|name|id|updated_at)){0,2}$",
                    "default": "sort_order"
                },
                "x-plnnrly-sort-keys": {
                    "sort_order": "Position in the catalogue, as ordered in plnnrly. This is the default sort.",
                    "name": "Subscription type name, A→Z.",
                    "id": "Id, ascending. An id never changes, so use this for a stable full export.",
                    "updated_at": "Last-write timestamp; pair with the id tiebreaker for sync."
                }
            },
            "SubtaskInclude": {
                "name": "include",
                "in": "query",
                "description": "Comma-separated names to inline, at most five. Each is a single name from the list below, never a dotted path: nested includes are not supported. `work_item`: The work item this subtask hangs off, with its issue key and title.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^(work_item)(,(work_item)){0,4}$"
                },
                "x-plnnrly-include-paths": {
                    "work_item": {
                        "description": "The work item this subtask hangs off, with its issue key and title.",
                        "kind": "relation",
                        "to_many": false
                    }
                }
            },
            "SubtaskPageNumber": {
                "name": "page",
                "in": "query",
                "description": "Opt in to page-number paging: 1-based, at most 1000. Omit it, the default, to page by opaque cursor, which is what links.next hands you. Cannot be combined with cursor. Published only on collections that count their rows on every request. This collection's default sort is a display position that changes whenever someone drags the list into a new order in plnnrly, so a page number can skip or repeat a row if the list is reordered between two page requests. These collections rarely exceed one page; for a stable walk, send sort=id and follow links.next without page.",
                "required": false,
                "schema": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 1000
                },
                "x-plnnrly-constraint": "This collection's default sort is a display position that changes whenever someone drags the list into a new order in plnnrly, so a page number can skip or repeat a row if the list is reordered between two page requests. These collections rarely exceed one page; for a stable walk, send sort=id and follow links.next without page."
            },
            "SubtaskSort": {
                "name": "sort",
                "in": "query",
                "description": "Up to three keys, comma-separated, each optionally prefixed with `-` for descending. `order`: Position in the work item's checklist, as ordered in plnnrly. This position changes whenever someone drags the list into a new order in plnnrly, so paging by number over it can skip or repeat a row. `title`: Subtask title, A→Z. `id`: Id, ascending. An id never changes, so use this for a stable full export. `updated_at`: Last-write timestamp; pair with the id tiebreaker for sync.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^-?(order|title|id|updated_at)(,-?(order|title|id|updated_at)){0,2}$",
                    "default": "order"
                },
                "x-plnnrly-sort-keys": {
                    "order": "Position in the work item's checklist, as ordered in plnnrly.",
                    "title": "Subtask title, A→Z.",
                    "id": "Id, ascending. An id never changes, so use this for a stable full export.",
                    "updated_at": "Last-write timestamp; pair with the id tiebreaker for sync."
                }
            },
            "TaskSort": {
                "name": "sort",
                "in": "query",
                "description": "Up to three keys, comma-separated, each optionally prefixed with `-` for descending. `order_column`: Position on the kanban board, lowest first. Server-assigned and changed when someone drags the card in plnnrly; ties are broken by id. This position changes whenever someone drags the list into a new order in plnnrly, so paging by number over it can skip or repeat a row. `due_date`: Day the task is due. `id`: Id, ascending. An id never changes, so use this for a stable full export. `updated_at`: Last-write timestamp; pair with the id tiebreaker for sync.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^-?(order_column|due_date|id|updated_at)(,-?(order_column|due_date|id|updated_at)){0,2}$",
                    "default": "order_column"
                },
                "x-plnnrly-sort-keys": {
                    "order_column": "Position on the kanban board, lowest first. Server-assigned and changed when someone drags the card in plnnrly; ties are broken by id.",
                    "due_date": "Day the task is due.",
                    "id": "Id, ascending. An id never changes, so use this for a stable full export.",
                    "updated_at": "Last-write timestamp; pair with the id tiebreaker for sync."
                }
            },
            "TimeInclude": {
                "name": "include",
                "in": "query",
                "description": "Comma-separated names to inline, at most five. Each is a single name from the list below, never a dotted path: nested includes are not supported. `budget`: The budget the hours are invoiced against. `task`: The task the hours are booked on, limited to the tasks you can see on the tasks collection. `user`: The employee the hours belong to.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^(budget|task|user)(,(budget|task|user)){0,4}$"
                },
                "x-plnnrly-include-paths": {
                    "budget": {
                        "description": "The budget the hours are invoiced against.",
                        "kind": "relation",
                        "to_many": false
                    },
                    "task": {
                        "description": "The task the hours are booked on, limited to the tasks you can see on the tasks collection.",
                        "kind": "relation",
                        "to_many": false
                    },
                    "user": {
                        "description": "The employee the hours belong to.",
                        "kind": "relation",
                        "to_many": false
                    }
                }
            },
            "TimeSort": {
                "name": "sort",
                "in": "query",
                "description": "Up to three keys, comma-separated, each optionally prefixed with `-` for descending. `date`: The calendar day the work happened, newest first. `id`: Id, ascending. An id never changes, so use this for a stable full export. `updated_at`: Last-write timestamp; pair with the id tiebreaker for sync.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^-?(date|id|updated_at)(,-?(date|id|updated_at)){0,2}$",
                    "default": "date"
                },
                "x-plnnrly-sort-keys": {
                    "date": "The calendar day the work happened, newest first.",
                    "id": "Id, ascending. An id never changes, so use this for a stable full export.",
                    "updated_at": "Last-write timestamp; pair with the id tiebreaker for sync."
                }
            },
            "TimerInclude": {
                "name": "include",
                "in": "query",
                "description": "Comma-separated names to inline, at most five. Each is a single name from the list below, never a dotted path: nested includes are not supported. `timeable`: The work item or planning event being timed. `user`: The employee the timer belongs to.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^(timeable|user)(,(timeable|user)){0,4}$"
                },
                "x-plnnrly-include-paths": {
                    "timeable": {
                        "description": "The work item or planning event being timed.",
                        "cost": "one extra lookup per record type on the page",
                        "kind": "relation",
                        "to_many": false
                    },
                    "user": {
                        "description": "The employee the timer belongs to.",
                        "kind": "relation",
                        "to_many": false
                    }
                }
            },
            "TimerSort": {
                "name": "sort",
                "in": "query",
                "description": "Up to three keys, comma-separated, each optionally prefixed with `-` for descending. `created_at`: When the timer row was created, newest first. `started_at`: When the stopwatch was started, oldest first. `id`: Id, ascending. An id never changes, so use this for a stable full export. `updated_at`: Last-write timestamp; pair with the id tiebreaker for sync.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^-?(created_at|started_at|id|updated_at)(,-?(created_at|started_at|id|updated_at)){0,2}$",
                    "default": "created_at"
                },
                "x-plnnrly-sort-keys": {
                    "created_at": "When the timer row was created, newest first.",
                    "started_at": "When the stopwatch was started, oldest first.",
                    "id": "Id, ascending. An id never changes, so use this for a stable full export.",
                    "updated_at": "Last-write timestamp; pair with the id tiebreaker for sync."
                }
            },
            "Trashed": {
                "name": "trashed",
                "in": "query",
                "description": "Whether soft-deleted rows are included.",
                "required": false,
                "schema": {
                    "type": "string",
                    "enum": [
                        "without",
                        "with",
                        "only"
                    ],
                    "default": "without"
                }
            },
            "UserSort": {
                "name": "sort",
                "in": "query",
                "description": "Up to three keys, comma-separated, each optionally prefixed with `-` for descending. `name`: Full name, A→Z, the order plnnrly's people list uses. `date_employed`: First day of employment, earliest first; the users with none sort with the nulls. `birthday`: Date of birth, oldest first; the users with none sort with the nulls. `id`: Id, ascending. An id never changes, so use this for a stable full export. `updated_at`: Last-write timestamp; pair with the id tiebreaker for sync.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^-?(name|date_employed|birthday|id|updated_at)(,-?(name|date_employed|birthday|id|updated_at)){0,2}$",
                    "default": "name"
                },
                "x-plnnrly-sort-keys": {
                    "name": "Full name, A→Z, the order plnnrly's people list uses.",
                    "date_employed": "First day of employment, earliest first; the users with none sort with the nulls.",
                    "birthday": "Date of birth, oldest first; the users with none sort with the nulls.",
                    "id": "Id, ascending. An id never changes, so use this for a stable full export.",
                    "updated_at": "Last-write timestamp; pair with the id tiebreaker for sync."
                }
            },
            "UserTodoInclude": {
                "name": "include",
                "in": "query",
                "description": "Comma-separated names to inline, at most five. Each is a single name from the list below, never a dotted path: nested includes are not supported. `user`: The user the todo belongs to, always the authenticated caller.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^(user)(,(user)){0,4}$"
                },
                "x-plnnrly-include-paths": {
                    "user": {
                        "description": "The user the todo belongs to, always the authenticated caller.",
                        "cost": "one query per page",
                        "kind": "relation",
                        "to_many": false
                    }
                }
            },
            "UserTodoSort": {
                "name": "sort",
                "in": "query",
                "description": "Up to three keys, comma-separated, each optionally prefixed with `-` for descending. `created_at`: Newest todo first. `id`: Id, ascending. An id never changes, so use this for a stable full export. `updated_at`: Last-write timestamp; pair with the id tiebreaker for sync.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^-?(created_at|id|updated_at)(,-?(created_at|id|updated_at)){0,2}$",
                    "default": "created_at"
                },
                "x-plnnrly-sort-keys": {
                    "created_at": "Newest todo first.",
                    "id": "Id, ascending. An id never changes, so use this for a stable full export.",
                    "updated_at": "Last-write timestamp; pair with the id tiebreaker for sync."
                }
            },
            "ViewDetail": {
                "name": "view",
                "in": "query",
                "description": "Which field set to render.",
                "required": false,
                "schema": {
                    "type": "string",
                    "enum": [
                        "summary",
                        "detail"
                    ],
                    "default": "detail"
                }
            },
            "ViewSummary": {
                "name": "view",
                "in": "query",
                "description": "Which field set to render.",
                "required": false,
                "schema": {
                    "type": "string",
                    "enum": [
                        "summary",
                        "detail"
                    ],
                    "default": "summary"
                }
            },
            "WithTotal": {
                "name": "with_total",
                "in": "query",
                "description": "Whether meta.page.total is counted for this request.",
                "required": false,
                "schema": {
                    "type": "boolean",
                    "default": false
                }
            },
            "WorkItemInclude": {
                "name": "include",
                "in": "query",
                "description": "Comma-separated names to inline, at most five. Each is a single name from the list below, never a dotted path: nested includes are not supported. `assignee`: The user the work is assigned to. Requires the users prefix's read grant and a token scope covering it. `comments`: Comments on this work item, capped at 25 rows. Truncation emits an `include_truncated` warning; `links.comments` is the paginated continuation. `drive_items`: Drive documents linked to this work item, capped at 25 rows and narrowed to the ones you may open. `links.drive_items` is the paginated list of all of them. Requires the `project_drive` prefix's access grant and a token scope covering it. `labels`: Labels on this work item in the project's label order, capped at 25 rows. While the Sprints feature is enabled, `links.labels` is the paginated list of all of them. `parent`: The parent work item, on a sub-issue. `reporter`: The user who filed the work item. Requires the users prefix's read grant and a token scope covering it. `sprint`: The sprint this item sits in, when it is not in the backlog. `subtasks`: Checklist subtasks on this work item, capped at 25 rows. `links.subtasks` is the paginated continuation. `work_type`: The work type of this item. Requires the `work_types` prefix's read grant and a token scope covering it.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^(assignee|comments|drive_items|labels|parent|reporter|sprint|subtasks|work_type)(,(assignee|comments|drive_items|labels|parent|reporter|sprint|subtasks|work_type)){0,4}$"
                },
                "x-plnnrly-include-paths": {
                    "assignee": {
                        "description": "The user the work is assigned to. Requires the users prefix's read grant and a token scope covering it.",
                        "kind": "relation",
                        "to_many": false
                    },
                    "comments": {
                        "description": "Comments on this work item, capped at 25 rows. Truncation emits an `include_truncated` warning; `links.comments` is the paginated continuation.",
                        "cost": "one extra lookup per page, plus one per type of comment author",
                        "kind": "relation",
                        "to_many": true
                    },
                    "drive_items": {
                        "description": "Drive documents linked to this work item, capped at 25 rows and narrowed to the ones you may open. `links.drive_items` is the paginated list of all of them. Requires the `project_drive` prefix's access grant and a token scope covering it.",
                        "cost": "one extra lookup per page, plus three to work out which drive items you may open",
                        "kind": "relation",
                        "to_many": true
                    },
                    "labels": {
                        "description": "Labels on this work item in the project's label order, capped at 25 rows. While the Sprints feature is enabled, `links.labels` is the paginated list of all of them.",
                        "cost": "one extra lookup per page",
                        "kind": "relation",
                        "to_many": true
                    },
                    "parent": {
                        "description": "The parent work item, on a sub-issue.",
                        "kind": "relation",
                        "to_many": false
                    },
                    "reporter": {
                        "description": "The user who filed the work item. Requires the users prefix's read grant and a token scope covering it.",
                        "kind": "relation",
                        "to_many": false
                    },
                    "sprint": {
                        "description": "The sprint this item sits in, when it is not in the backlog.",
                        "kind": "relation",
                        "to_many": false
                    },
                    "subtasks": {
                        "description": "Checklist subtasks on this work item, capped at 25 rows. `links.subtasks` is the paginated continuation.",
                        "cost": "one extra query per page",
                        "kind": "relation",
                        "to_many": true
                    },
                    "work_type": {
                        "description": "The work type of this item. Requires the `work_types` prefix's read grant and a token scope covering it.",
                        "kind": "relation",
                        "to_many": false
                    }
                }
            },
            "WorkItemLinkInclude": {
                "name": "include",
                "in": "query",
                "description": "Comma-separated names to inline, at most five. Each is a single name from the list below, never a dotted path: nested includes are not supported. `source_work_item`: Work item the link points away from, with its issue key and title. `target_work_item`: Work item the link points at, with its issue key and title.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^(source_work_item|target_work_item)(,(source_work_item|target_work_item)){0,4}$"
                },
                "x-plnnrly-include-paths": {
                    "source_work_item": {
                        "description": "Work item the link points away from, with its issue key and title.",
                        "kind": "relation",
                        "to_many": false
                    },
                    "target_work_item": {
                        "description": "Work item the link points at, with its issue key and title.",
                        "kind": "relation",
                        "to_many": false
                    }
                }
            },
            "WorkItemLinkSort": {
                "name": "sort",
                "in": "query",
                "description": "Up to three keys, comma-separated, each optionally prefixed with `-` for descending. `id`: Id, ascending. An id never changes, so use this for a stable full export. `updated_at`: Last-write timestamp; pair with the id tiebreaker for sync.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^-?(id|updated_at)(,-?(id|updated_at)){0,2}$",
                    "default": "id"
                },
                "x-plnnrly-sort-keys": {
                    "id": "Id, ascending. An id never changes, so use this for a stable full export.",
                    "updated_at": "Last-write timestamp; pair with the id tiebreaker for sync."
                }
            },
            "WorkItemSort": {
                "name": "sort",
                "in": "query",
                "description": "Up to three keys, comma-separated, each optionally prefixed with `-` for descending. `order`: Position within the sprint or backlog, as ordered in plnnrly. The default sort, ascending. This position changes whenever someone drags the list into a new order in plnnrly, so paging by number over it can skip or repeat a row. `column_order`: Position within the board column. `title`: Work item title, A→Z. `due_date`: Due date, earliest first. `priority`: Priority, ordered by its stored value rather than by severity: high, highest, low, lowest, normal. `id`: Id, ascending. An id never changes, so use this for a stable full export. `updated_at`: Last-write timestamp; pair with the id tiebreaker for sync.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^-?(order|column_order|title|due_date|priority|id|updated_at)(,-?(order|column_order|title|due_date|priority|id|updated_at)){0,2}$",
                    "default": "order"
                },
                "x-plnnrly-sort-keys": {
                    "order": "Position within the sprint or backlog, as ordered in plnnrly. The default sort, ascending.",
                    "column_order": "Position within the board column.",
                    "title": "Work item title, A→Z.",
                    "due_date": "Due date, earliest first.",
                    "priority": "Priority, ordered by its stored value rather than by severity: high, highest, low, lowest, normal.",
                    "id": "Id, ascending. An id never changes, so use this for a stable full export.",
                    "updated_at": "Last-write timestamp; pair with the id tiebreaker for sync."
                }
            },
            "WorkTypePageNumber": {
                "name": "page",
                "in": "query",
                "description": "Opt in to page-number paging: 1-based, at most 1000. Omit it, the default, to page by opaque cursor, which is what links.next hands you. Cannot be combined with cursor. Published only on collections that count their rows on every request. This collection's default sort is a display position that changes whenever someone drags the list into a new order in plnnrly, so a page number can skip or repeat a row if the list is reordered between two page requests. These collections rarely exceed one page; for a stable walk, send sort=id and follow links.next without page.",
                "required": false,
                "schema": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 1000
                },
                "x-plnnrly-constraint": "This collection's default sort is a display position that changes whenever someone drags the list into a new order in plnnrly, so a page number can skip or repeat a row if the list is reordered between two page requests. These collections rarely exceed one page; for a stable walk, send sort=id and follow links.next without page."
            },
            "WorkTypeSort": {
                "name": "sort",
                "in": "query",
                "description": "Up to three keys, comma-separated, each optionally prefixed with `-` for descending. `order`: Display position, as ordered in plnnrly. This position changes whenever someone drags the list into a new order in plnnrly, so paging by number over it can skip or repeat a row. `name`: Work-type name, A→Z. `id`: Id, ascending. An id never changes, so use this for a stable full export. `updated_at`: Last-write timestamp; pair with the id tiebreaker for sync.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^-?(order|name|id|updated_at)(,-?(order|name|id|updated_at)){0,2}$",
                    "default": "order"
                },
                "x-plnnrly-sort-keys": {
                    "order": "Display position, as ordered in plnnrly.",
                    "name": "Work-type name, A→Z.",
                    "id": "Id, ascending. An id never changes, so use this for a stable full export.",
                    "updated_at": "Last-write timestamp; pair with the id tiebreaker for sync."
                }
            },
            "WorkflowSort": {
                "name": "sort",
                "in": "query",
                "description": "Up to three keys, comma-separated, each optionally prefixed with `-` for descending. `name`: Workflow name, A→Z. `order`: Display position, as ordered in plnnrly. This position changes whenever someone drags the list into a new order in plnnrly, so paging by number over it can skip or repeat a row. `id`: Id, ascending. An id never changes, so use this for a stable full export. `updated_at`: Last-write timestamp; pair with the id tiebreaker for sync.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^-?(name|order|id|updated_at)(,-?(name|order|id|updated_at)){0,2}$",
                    "default": "name"
                },
                "x-plnnrly-sort-keys": {
                    "name": "Workflow name, A→Z.",
                    "order": "Display position, as ordered in plnnrly.",
                    "id": "Id, ascending. An id never changes, so use this for a stable full export.",
                    "updated_at": "Last-write timestamp; pair with the id tiebreaker for sync."
                }
            },
            "WorkflowStatusInclude": {
                "name": "include",
                "in": "query",
                "description": "Comma-separated names to inline, at most five. Each is a single name from the list below, never a dotted path: nested includes are not supported. `workflow`: Workflow this status belongs to, with its name.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^(workflow)(,(workflow)){0,4}$"
                },
                "x-plnnrly-include-paths": {
                    "workflow": {
                        "description": "Workflow this status belongs to, with its name.",
                        "kind": "relation",
                        "to_many": false
                    }
                }
            },
            "WorkflowStatusPageNumber": {
                "name": "page",
                "in": "query",
                "description": "Opt in to page-number paging: 1-based, at most 1000. Omit it, the default, to page by opaque cursor, which is what links.next hands you. Cannot be combined with cursor. Published only on collections that count their rows on every request. This collection's default sort is a display position that changes whenever someone drags the list into a new order in plnnrly, so a page number can skip or repeat a row if the list is reordered between two page requests. These collections rarely exceed one page; for a stable walk, send sort=id and follow links.next without page.",
                "required": false,
                "schema": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 1000
                },
                "x-plnnrly-constraint": "This collection's default sort is a display position that changes whenever someone drags the list into a new order in plnnrly, so a page number can skip or repeat a row if the list is reordered between two page requests. These collections rarely exceed one page; for a stable walk, send sort=id and follow links.next without page."
            },
            "WorkflowStatusSort": {
                "name": "sort",
                "in": "query",
                "description": "Up to three keys, comma-separated, each optionally prefixed with `-` for descending. `order`: Position within the workflow, left to right on the board. This position changes whenever someone drags the list into a new order in plnnrly, so paging by number over it can skip or repeat a row. `name`: Status name, A→Z. `id`: Id, ascending. An id never changes, so use this for a stable full export. `updated_at`: Last-write timestamp; pair with the id tiebreaker for sync.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^-?(order|name|id|updated_at)(,-?(order|name|id|updated_at)){0,2}$",
                    "default": "order"
                },
                "x-plnnrly-sort-keys": {
                    "order": "Position within the workflow, left to right on the board.",
                    "name": "Status name, A→Z.",
                    "id": "Id, ascending. An id never changes, so use this for a stable full export.",
                    "updated_at": "Last-write timestamp; pair with the id tiebreaker for sync."
                }
            },
            "WorkflowTransitionInclude": {
                "name": "include",
                "in": "query",
                "description": "Comma-separated names to inline, at most five. Each is a single name from the list below, never a dotted path: nested includes are not supported. `from_status`: Status the edge leads out of, with its colour and resolution flag. `to_status`: Status the edge leads into, with its colour and resolution flag. `workflow`: Workflow this edge belongs to.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^(from_status|to_status|workflow)(,(from_status|to_status|workflow)){0,4}$"
                },
                "x-plnnrly-include-paths": {
                    "from_status": {
                        "description": "Status the edge leads out of, with its colour and resolution flag.",
                        "kind": "relation",
                        "to_many": false
                    },
                    "to_status": {
                        "description": "Status the edge leads into, with its colour and resolution flag.",
                        "kind": "relation",
                        "to_many": false
                    },
                    "workflow": {
                        "description": "Workflow this edge belongs to.",
                        "kind": "relation",
                        "to_many": false
                    }
                }
            },
            "WorkflowTransitionSort": {
                "name": "sort",
                "in": "query",
                "description": "Up to three keys, comma-separated, each optionally prefixed with `-` for descending. `name`: Transition name, A→Z. `id`: Id, ascending. An id never changes, so use this for a stable full export. `updated_at`: Last-write timestamp; pair with the id tiebreaker for sync.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^-?(name|id|updated_at)(,-?(name|id|updated_at)){0,2}$",
                    "default": "id"
                },
                "x-plnnrly-sort-keys": {
                    "name": "Transition name, A→Z.",
                    "id": "Id, ascending. An id never changes, so use this for a stable full export.",
                    "updated_at": "Last-write timestamp; pair with the id tiebreaker for sync."
                }
            },
            "XRequestId": {
                "name": "X-Request-Id",
                "in": "header",
                "description": "A correlation id of your own. It is recorded in our logs next to the server's request id but never returned: the response header always carries the server's id. A value that does not match the pattern is discarded silently, never rejected.",
                "required": false,
                "schema": {
                    "type": "string",
                    "pattern": "^[A-Za-z0-9_-]{1,64}$"
                }
            }
        },
        "responses": {
            "Problem400": {
                "description": "Bad request.",
                "content": {
                    "application/problem+json": {
                        "schema": {
                            "oneOf": [
                                {
                                    "$ref": "#/components/schemas/IdempotencyKeyRequiredProblem"
                                },
                                {
                                    "$ref": "#/components/schemas/InvalidCursorProblem"
                                },
                                {
                                    "$ref": "#/components/schemas/InvalidQueryParameterProblem"
                                },
                                {
                                    "$ref": "#/components/schemas/MalformedRequestProblem"
                                },
                                {
                                    "$ref": "#/components/schemas/UnsupportedParameterProblem"
                                }
                            ],
                            "discriminator": {
                                "propertyName": "type",
                                "mapping": {
                                    "https://developers.plnnrly.com/api/v2/problems/idempotency-key-required": "#/components/schemas/IdempotencyKeyRequiredProblem",
                                    "https://developers.plnnrly.com/api/v2/problems/invalid-cursor": "#/components/schemas/InvalidCursorProblem",
                                    "https://developers.plnnrly.com/api/v2/problems/invalid-query-parameter": "#/components/schemas/InvalidQueryParameterProblem",
                                    "https://developers.plnnrly.com/api/v2/problems/malformed-request": "#/components/schemas/MalformedRequestProblem",
                                    "https://developers.plnnrly.com/api/v2/problems/unsupported-parameter": "#/components/schemas/UnsupportedParameterProblem"
                                }
                            }
                        },
                        "examples": {
                            "idempotency-key-required": {
                                "$ref": "#/components/examples/IdempotencyKeyRequiredProblem"
                            },
                            "invalid-cursor": {
                                "$ref": "#/components/examples/InvalidCursorProblem"
                            },
                            "invalid-query-parameter": {
                                "$ref": "#/components/examples/InvalidQueryParameterProblem"
                            },
                            "malformed-request": {
                                "$ref": "#/components/examples/MalformedRequestProblem"
                            },
                            "unsupported-parameter": {
                                "$ref": "#/components/examples/UnsupportedParameterProblem"
                            }
                        }
                    }
                }
            },
            "Problem401": {
                "description": "Unauthenticated.",
                "content": {
                    "application/problem+json": {
                        "schema": {
                            "oneOf": [
                                {
                                    "$ref": "#/components/schemas/TokenExpiredProblem"
                                },
                                {
                                    "$ref": "#/components/schemas/UnauthenticatedProblem"
                                }
                            ],
                            "discriminator": {
                                "propertyName": "type",
                                "mapping": {
                                    "https://developers.plnnrly.com/api/v2/problems/token-expired": "#/components/schemas/TokenExpiredProblem",
                                    "https://developers.plnnrly.com/api/v2/problems/unauthenticated": "#/components/schemas/UnauthenticatedProblem"
                                }
                            }
                        },
                        "examples": {
                            "token-expired": {
                                "$ref": "#/components/examples/TokenExpiredProblem"
                            },
                            "unauthenticated": {
                                "$ref": "#/components/examples/UnauthenticatedProblem"
                            }
                        }
                    }
                }
            },
            "Problem403": {
                "description": "Forbidden.",
                "content": {
                    "application/problem+json": {
                        "schema": {
                            "oneOf": [
                                {
                                    "$ref": "#/components/schemas/ForbiddenByPolicyProblem"
                                },
                                {
                                    "$ref": "#/components/schemas/InsufficientPermissionProblem"
                                },
                                {
                                    "$ref": "#/components/schemas/InsufficientTokenScopeProblem"
                                }
                            ],
                            "discriminator": {
                                "propertyName": "type",
                                "mapping": {
                                    "https://developers.plnnrly.com/api/v2/problems/forbidden-by-policy": "#/components/schemas/ForbiddenByPolicyProblem",
                                    "https://developers.plnnrly.com/api/v2/problems/insufficient-permission": "#/components/schemas/InsufficientPermissionProblem",
                                    "https://developers.plnnrly.com/api/v2/problems/insufficient-token-scope": "#/components/schemas/InsufficientTokenScopeProblem"
                                }
                            }
                        },
                        "examples": {
                            "forbidden-by-policy": {
                                "$ref": "#/components/examples/ForbiddenByPolicyProblem"
                            },
                            "insufficient-permission": {
                                "$ref": "#/components/examples/InsufficientPermissionProblem"
                            },
                            "insufficient-token-scope": {
                                "$ref": "#/components/examples/InsufficientTokenScopeProblem"
                            }
                        }
                    }
                }
            },
            "Problem404": {
                "description": "Not found.",
                "content": {
                    "application/problem+json": {
                        "schema": {
                            "oneOf": [
                                {
                                    "$ref": "#/components/schemas/EndpointNotFoundProblem"
                                },
                                {
                                    "$ref": "#/components/schemas/ResourceNotFoundProblem"
                                }
                            ],
                            "discriminator": {
                                "propertyName": "type",
                                "mapping": {
                                    "https://developers.plnnrly.com/api/v2/problems/endpoint-not-found": "#/components/schemas/EndpointNotFoundProblem",
                                    "https://developers.plnnrly.com/api/v2/problems/resource-not-found": "#/components/schemas/ResourceNotFoundProblem"
                                }
                            }
                        },
                        "examples": {
                            "endpoint-not-found": {
                                "$ref": "#/components/examples/EndpointNotFoundProblem"
                            },
                            "resource-not-found": {
                                "$ref": "#/components/examples/ResourceNotFoundProblem"
                            }
                        }
                    }
                }
            },
            "Problem405": {
                "description": "Method not allowed.",
                "content": {
                    "application/problem+json": {
                        "schema": {
                            "$ref": "#/components/schemas/MethodNotAllowedProblem"
                        }
                    }
                }
            },
            "Problem406": {
                "description": "Not acceptable.",
                "content": {
                    "application/problem+json": {
                        "schema": {
                            "$ref": "#/components/schemas/NotAcceptableProblem"
                        },
                        "examples": {
                            "not-acceptable": {
                                "$ref": "#/components/examples/NotAcceptableProblem"
                            }
                        }
                    }
                }
            },
            "Problem409Idempotency": {
                "description": "Conflict.",
                "content": {
                    "application/problem+json": {
                        "schema": {
                            "oneOf": [
                                {
                                    "$ref": "#/components/schemas/IdempotencyKeyInFlightProblem"
                                },
                                {
                                    "$ref": "#/components/schemas/IdempotencyKeyReusedProblem"
                                }
                            ],
                            "discriminator": {
                                "propertyName": "type",
                                "mapping": {
                                    "https://developers.plnnrly.com/api/v2/problems/idempotency-key-in-flight": "#/components/schemas/IdempotencyKeyInFlightProblem",
                                    "https://developers.plnnrly.com/api/v2/problems/idempotency-key-reused": "#/components/schemas/IdempotencyKeyReusedProblem"
                                }
                            }
                        },
                        "examples": {
                            "idempotency-key-in-flight": {
                                "$ref": "#/components/examples/IdempotencyKeyInFlightProblem"
                            },
                            "idempotency-key-reused": {
                                "$ref": "#/components/examples/IdempotencyKeyReusedProblem"
                            }
                        }
                    }
                }
            },
            "Problem412": {
                "description": "Precondition failed.",
                "content": {
                    "application/problem+json": {
                        "schema": {
                            "$ref": "#/components/schemas/PreconditionFailedProblem"
                        },
                        "examples": {
                            "precondition-failed": {
                                "$ref": "#/components/examples/PreconditionFailedProblem"
                            }
                        }
                    }
                }
            },
            "Problem415": {
                "description": "Unsupported media type.",
                "content": {
                    "application/problem+json": {
                        "schema": {
                            "$ref": "#/components/schemas/UnsupportedMediaTypeProblem"
                        },
                        "examples": {
                            "unsupported-media-type": {
                                "$ref": "#/components/examples/UnsupportedMediaTypeProblem"
                            }
                        }
                    }
                }
            },
            "Problem422": {
                "description": "Unprocessable content.",
                "content": {
                    "application/problem+json": {
                        "schema": {
                            "oneOf": [
                                {
                                    "$ref": "#/components/schemas/UnprocessableRelationshipProblem"
                                },
                                {
                                    "$ref": "#/components/schemas/ValidationFailedProblem"
                                }
                            ],
                            "discriminator": {
                                "propertyName": "type",
                                "mapping": {
                                    "https://developers.plnnrly.com/api/v2/problems/unprocessable-relationship": "#/components/schemas/UnprocessableRelationshipProblem",
                                    "https://developers.plnnrly.com/api/v2/problems/validation-failed": "#/components/schemas/ValidationFailedProblem"
                                }
                            }
                        },
                        "examples": {
                            "unprocessable-relationship": {
                                "$ref": "#/components/examples/UnprocessableRelationshipProblem"
                            },
                            "validation-failed": {
                                "$ref": "#/components/examples/ValidationFailedProblem"
                            }
                        }
                    }
                }
            },
            "Problem429": {
                "description": "Too many requests.",
                "content": {
                    "application/problem+json": {
                        "schema": {
                            "$ref": "#/components/schemas/RateLimitExceededProblem"
                        },
                        "examples": {
                            "rate-limit-exceeded": {
                                "$ref": "#/components/examples/RateLimitExceededProblem"
                            }
                        }
                    }
                }
            },
            "Problem500": {
                "description": "Server error.",
                "content": {
                    "application/problem+json": {
                        "schema": {
                            "$ref": "#/components/schemas/InternalErrorProblem"
                        },
                        "examples": {
                            "internal-error": {
                                "$ref": "#/components/examples/InternalErrorProblem"
                            }
                        }
                    }
                }
            },
            "Problem503": {
                "description": "Service unavailable.",
                "content": {
                    "application/problem+json": {
                        "schema": {
                            "$ref": "#/components/schemas/TenantUnavailableProblem"
                        },
                        "examples": {
                            "tenant-unavailable": {
                                "$ref": "#/components/examples/TenantUnavailableProblem"
                            }
                        }
                    }
                }
            }
        },
        "schemas": {
            "Absence": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "user_id": {
                        "type": "integer",
                        "description": "The employee this absence is for. Writing it requires the `absences::manage` permission."
                    },
                    "user": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/UserSummary"
                            }
                        ],
                        "description": "The employee the absence is for. Renders as an `{\"id\": N}` reference unless expanded, and stays a reference when the employee has since been deleted."
                    },
                    "manager_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Manager who filed or approved the absence, or `null` when none was recorded. The foreign key nulls on delete, so removing the manager leaves the absence behind unattributed."
                    },
                    "manager": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/UserSummary"
                            },
                            {
                                "type": "null"
                            }
                        ],
                        "description": "The manager who filed or approved the absence; absent when none was recorded."
                    },
                    "start": {
                        "type": "string",
                        "format": "date-time",
                        "description": "When the absence begins. RFC 3339 UTC."
                    },
                    "end": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "When the absence ends, or `null` while it is still running. An ongoing absence reaches through now everywhere it is read (planning capacity and reporting both count it), and that reach is uncapped, so an absence nobody closed keeps consuming capacity. RFC 3339 UTC."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "links": {
                        "$ref": "#/components/schemas/AbsenceLinks"
                    }
                },
                "required": [
                    "id",
                    "user_id",
                    "user",
                    "manager_id",
                    "manager",
                    "start",
                    "end",
                    "created_at",
                    "updated_at",
                    "links"
                ],
                "description": "An absence: the period an employee was away from work, booked for them by HR. An absence with no `end` is still running and reaches through now everywhere it is read, so planning capacity and reporting both keep counting it until someone closes it. The free-text note absences once carried was purged as health data and is not exposed here."
            },
            "AbsenceLinks": {
                "type": "object",
                "properties": {
                    "self": {
                        "type": "string",
                        "format": "uri",
                        "description": "The canonical URI of this record."
                    }
                },
                "required": [
                    "self"
                ],
                "additionalProperties": false
            },
            "AbsenceList": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "user_id": {
                        "type": "integer",
                        "description": "The employee this absence is for. Writing it requires the `absences::manage` permission."
                    },
                    "user": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/UserSummary"
                            }
                        ],
                        "description": "The employee the absence is for. Renders as an `{\"id\": N}` reference unless expanded, and stays a reference when the employee has since been deleted."
                    },
                    "manager_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Manager who filed or approved the absence, or `null` when none was recorded. The foreign key nulls on delete, so removing the manager leaves the absence behind unattributed."
                    },
                    "manager": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/UserSummary"
                            },
                            {
                                "type": "null"
                            }
                        ],
                        "description": "The manager who filed or approved the absence; absent when none was recorded."
                    },
                    "start": {
                        "type": "string",
                        "format": "date-time",
                        "description": "When the absence begins. RFC 3339 UTC."
                    },
                    "end": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "When the absence ends, or `null` while it is still running. An ongoing absence reaches through now everywhere it is read (planning capacity and reporting both count it), and that reach is uncapped, so an absence nobody closed keeps consuming capacity. RFC 3339 UTC."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "links": {
                        "$ref": "#/components/schemas/AbsenceLinks"
                    }
                },
                "required": [
                    "id",
                    "user_id",
                    "user",
                    "manager_id",
                    "manager",
                    "start",
                    "end",
                    "created_at",
                    "updated_at",
                    "links"
                ],
                "description": "An absence: the period an employee was away from work, booked for them by HR. An absence with no `end` is still running and reaches through now everywhere it is read, so planning capacity and reporting both keep counting it until someone closes it. The free-text note absences once carried was purged as health data and is not exposed here."
            },
            "AbsenceSummary": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "start": {
                        "type": "string",
                        "format": "date-time",
                        "description": "When the absence begins. RFC 3339 UTC."
                    },
                    "end": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "When the absence ends, or `null` while it is still running."
                    }
                },
                "required": [
                    "id",
                    "start",
                    "end"
                ],
                "description": "An absence, as referenced from another resource."
            },
            "AbsencesIndexMeta": {
                "type": "object",
                "properties": {
                    "page": {
                        "$ref": "#/components/schemas/Page"
                    }
                },
                "additionalProperties": false,
                "required": [
                    "page"
                ]
            },
            "Budget": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "project_id": {
                        "type": "integer",
                        "description": "Owning project. Settable at create time only; a budget cannot be moved to another project."
                    },
                    "budget_group_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Budget group this budget is filed under, `null` when ungrouped."
                    },
                    "order_column": {
                        "type": "integer",
                        "description": "Position within the project as arranged in plnnrly. Set on create to the highest existing position plus one, and renumbered 1..n per project whenever the project's budget page is saved in plnnrly. Not writable.",
                        "x-plnnrly-server-assigned": true
                    },
                    "description": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Budget description as shown in the UI. This is the budget's label and the only field `?q=` searches; a budget has no title."
                    },
                    "start_date": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date",
                        "description": "First day the budget covers, `null` when it has no start."
                    },
                    "end_date": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date",
                        "description": "Last day the budget covers, `null` when it has no end."
                    },
                    "budget": {
                        "type": "integer",
                        "description": "Budgeted amount of work, in minutes."
                    },
                    "spendable": {
                        "type": "integer",
                        "description": "The portion of the budget that may be allocated to tasks, in minutes. A stored value, never decremented by logged time."
                    },
                    "status": {
                        "type": "string",
                        "enum": [
                            "active",
                            "inactive",
                            "blocked"
                        ],
                        "x-extensible-enum": true,
                        "description": "Lifecycle status of the budget."
                    },
                    "is_open": {
                        "type": "boolean",
                        "description": "Whether the budget is open-ended: when `true` the spendable cap is ignored, plnnrly shows \"open\" in place of a minute total and the over-budget warning is skipped."
                    },
                    "is_billable": {
                        "type": "boolean",
                        "description": "Whether time logged on this budget is billable."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "project": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/ProjectSummary"
                            }
                        ],
                        "description": "The project this budget belongs to."
                    },
                    "budget_group": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/BudgetGroupSummary"
                            },
                            {
                                "type": "null"
                            }
                        ],
                        "description": "The budget group this budget is filed under."
                    },
                    "tasks_count": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Number of kanban tasks on this budget that the caller may see."
                    },
                    "links": {
                        "$ref": "#/components/schemas/BudgetLinks"
                    }
                },
                "required": [
                    "id",
                    "project_id",
                    "budget_group_id",
                    "order_column",
                    "description",
                    "start_date",
                    "end_date",
                    "budget",
                    "spendable",
                    "status",
                    "is_open",
                    "is_billable",
                    "created_at",
                    "updated_at",
                    "project",
                    "budget_group",
                    "tasks_count",
                    "links"
                ],
                "description": "A budget: a named pot of minutes on a project that tasks are filed under and time is logged against, optionally filed in an organisation-wide budget group. Deleting one is permanent and takes its tasks with it; a budget with logged time cannot be deleted."
            },
            "BudgetGroup": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Budget group name as shown in the UI. This is the default sort key."
                    },
                    "icon": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Icon identifier rendered beside the group, e.g. `heroicon-o-rectangle-stack`."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "budgets_count": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Number of budgets filed under this group."
                    },
                    "links": {
                        "$ref": "#/components/schemas/BudgetGroupLinks"
                    }
                },
                "required": [
                    "id",
                    "name",
                    "icon",
                    "created_at",
                    "updated_at",
                    "budgets_count",
                    "links"
                ],
                "description": "A budget group: the organisation-wide category budgets are filed under, carrying the icon plnnrly shows beside them. Deleting one ungroups its budgets permanently."
            },
            "BudgetGroupLinks": {
                "type": "object",
                "properties": {
                    "self": {
                        "type": "string",
                        "format": "uri",
                        "description": "The canonical URI of this record."
                    },
                    "budgets": {
                        "type": "string",
                        "format": "uri",
                        "description": "Budgets filed under this group."
                    }
                },
                "required": [
                    "self",
                    "budgets"
                ],
                "additionalProperties": false
            },
            "BudgetGroupList": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Budget group name as shown in the UI. This is the default sort key."
                    },
                    "icon": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Icon identifier rendered beside the group, e.g. `heroicon-o-rectangle-stack`."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "budgets_count": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Number of budgets filed under this group."
                    },
                    "links": {
                        "$ref": "#/components/schemas/BudgetGroupLinks"
                    }
                },
                "required": [
                    "id",
                    "name",
                    "icon",
                    "created_at",
                    "updated_at",
                    "budgets_count",
                    "links"
                ],
                "description": "A budget group: the organisation-wide category budgets are filed under, carrying the icon plnnrly shows beside them. Deleting one ungroups its budgets permanently."
            },
            "BudgetGroupSummary": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Budget group name as shown in the UI."
                    }
                },
                "required": [
                    "id",
                    "name"
                ],
                "description": "A budget group, as referenced from another resource."
            },
            "BudgetGroupsIndexMeta": {
                "type": "object",
                "properties": {
                    "page": {
                        "$ref": "#/components/schemas/Page"
                    }
                },
                "additionalProperties": false,
                "required": [
                    "page"
                ]
            },
            "BudgetLinks": {
                "type": "object",
                "properties": {
                    "self": {
                        "type": "string",
                        "format": "uri",
                        "description": "The canonical URI of this record."
                    },
                    "tasks": {
                        "type": "string",
                        "format": "uri",
                        "description": "Kanban tasks filed under this budget."
                    }
                },
                "required": [
                    "self",
                    "tasks"
                ],
                "additionalProperties": false
            },
            "BudgetList": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "project_id": {
                        "type": "integer",
                        "description": "Owning project. Settable at create time only; a budget cannot be moved to another project."
                    },
                    "budget_group_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Budget group this budget is filed under, `null` when ungrouped."
                    },
                    "order_column": {
                        "type": "integer",
                        "description": "Position within the project as arranged in plnnrly. Set on create to the highest existing position plus one, and renumbered 1..n per project whenever the project's budget page is saved in plnnrly. Not writable.",
                        "x-plnnrly-server-assigned": true
                    },
                    "description": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Budget description as shown in the UI. This is the budget's label and the only field `?q=` searches; a budget has no title."
                    },
                    "start_date": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date",
                        "description": "First day the budget covers, `null` when it has no start."
                    },
                    "end_date": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date",
                        "description": "Last day the budget covers, `null` when it has no end."
                    },
                    "budget": {
                        "type": "integer",
                        "description": "Budgeted amount of work, in minutes."
                    },
                    "spendable": {
                        "type": "integer",
                        "description": "The portion of the budget that may be allocated to tasks, in minutes. A stored value, never decremented by logged time."
                    },
                    "status": {
                        "type": "string",
                        "enum": [
                            "active",
                            "inactive",
                            "blocked"
                        ],
                        "x-extensible-enum": true,
                        "description": "Lifecycle status of the budget."
                    },
                    "is_open": {
                        "type": "boolean",
                        "description": "Whether the budget is open-ended: when `true` the spendable cap is ignored, plnnrly shows \"open\" in place of a minute total and the over-budget warning is skipped."
                    },
                    "is_billable": {
                        "type": "boolean",
                        "description": "Whether time logged on this budget is billable."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "project": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/ProjectSummary"
                            }
                        ],
                        "description": "The project this budget belongs to."
                    },
                    "budget_group": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/BudgetGroupSummary"
                            },
                            {
                                "type": "null"
                            }
                        ],
                        "description": "The budget group this budget is filed under."
                    },
                    "tasks_count": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Number of kanban tasks on this budget that the caller may see."
                    },
                    "links": {
                        "$ref": "#/components/schemas/BudgetLinks"
                    }
                },
                "required": [
                    "id",
                    "project_id",
                    "budget_group_id",
                    "order_column",
                    "description",
                    "start_date",
                    "end_date",
                    "budget",
                    "spendable",
                    "status",
                    "is_open",
                    "is_billable",
                    "created_at",
                    "updated_at",
                    "project",
                    "budget_group",
                    "tasks_count",
                    "links"
                ],
                "description": "A budget: a named pot of minutes on a project that tasks are filed under and time is logged against, optionally filed in an organisation-wide budget group. Deleting one is permanent and takes its tasks with it; a budget with logged time cannot be deleted."
            },
            "BudgetSummary": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "description": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Budget description as shown in the UI."
                    }
                },
                "required": [
                    "id",
                    "description"
                ],
                "description": "A budget, as referenced from another resource."
            },
            "BudgetsIndexMeta": {
                "type": "object",
                "properties": {
                    "page": {
                        "$ref": "#/components/schemas/Page"
                    }
                },
                "additionalProperties": false,
                "required": [
                    "page"
                ]
            },
            "Checkin": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "user_id": {
                        "type": "integer",
                        "description": "Employee this checkin belongs to. Assigned from the authenticated user on create and never writable.",
                        "x-plnnrly-server-assigned": true
                    },
                    "user": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/UserSummary"
                            }
                        ],
                        "description": "The employee this checkin belongs to. Expanding it with `?include=user` additionally requires the `users::read` permission and a token scope covering the users prefix."
                    },
                    "checked_in_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "When the employee clocked in."
                    },
                    "checked_out_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "When the employee clocked out, or `null` while the checkin is still open. A `null` value marks the checkin plnnrly shows as active."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "links": {
                        "$ref": "#/components/schemas/CheckinLinks"
                    }
                },
                "required": [
                    "id",
                    "user_id",
                    "user",
                    "checked_in_at",
                    "checked_out_at",
                    "created_at",
                    "updated_at",
                    "links"
                ],
                "description": "A checkin: one attendance record for one employee, opened when they clock in and closed when they clock out. A `null` `checked_out_at` means the checkin is still open: that is the only thing distinguishing an in-progress day from a finished one, and `filter[checked_out_at][null]=true` is how a client asks for it. Both timestamps are UTC; rendering them in the employee's own zone is a client concern."
            },
            "CheckinLinks": {
                "type": "object",
                "properties": {
                    "self": {
                        "type": "string",
                        "format": "uri",
                        "description": "The canonical URI of this record."
                    }
                },
                "required": [
                    "self"
                ],
                "additionalProperties": false
            },
            "CheckinList": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "user_id": {
                        "type": "integer",
                        "description": "Employee this checkin belongs to. Assigned from the authenticated user on create and never writable.",
                        "x-plnnrly-server-assigned": true
                    },
                    "user": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/UserSummary"
                            }
                        ],
                        "description": "The employee this checkin belongs to. Expanding it with `?include=user` additionally requires the `users::read` permission and a token scope covering the users prefix."
                    },
                    "checked_in_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "When the employee clocked in."
                    },
                    "checked_out_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "When the employee clocked out, or `null` while the checkin is still open. A `null` value marks the checkin plnnrly shows as active."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "links": {
                        "$ref": "#/components/schemas/CheckinLinks"
                    }
                },
                "required": [
                    "id",
                    "user_id",
                    "user",
                    "checked_in_at",
                    "checked_out_at",
                    "created_at",
                    "updated_at",
                    "links"
                ],
                "description": "A checkin: one attendance record for one employee, opened when they clock in and closed when they clock out. A `null` `checked_out_at` means the checkin is still open: that is the only thing distinguishing an in-progress day from a finished one, and `filter[checked_out_at][null]=true` is how a client asks for it. Both timestamps are UTC; rendering them in the employee's own zone is a client concern."
            },
            "CheckinSummary": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "checked_in_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "When the employee clocked in."
                    }
                },
                "required": [
                    "id",
                    "checked_in_at"
                ],
                "description": "A checkin, as referenced from another resource."
            },
            "CheckinsIndexMeta": {
                "type": "object",
                "properties": {
                    "page": {
                        "$ref": "#/components/schemas/PageOptionalTotal"
                    }
                },
                "additionalProperties": false,
                "required": [
                    "page"
                ]
            },
            "Comment": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "body": {
                        "type": "string",
                        "format": "html",
                        "description": "Comment body as rich text. HTML from the plnnrly editor, sanitised on the way out, so what you read back can differ from the bytes you sent. Mention spans (`<span data-type=\"mention\" data-id=\"3\">`) survive untouched and may be posted back verbatim; constructing a new mention is not supported in v2.0."
                    },
                    "author_type": {
                        "type": "string",
                        "enum": [
                            "user"
                        ],
                        "x-extensible-enum": true,
                        "description": "The kind of record `author` points at; it says which branch of `author` applies."
                    },
                    "author_id": {
                        "type": "integer",
                        "description": "The id of the record `author` points at."
                    },
                    "author": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/UserSummary"
                            }
                        ],
                        "x-plnnrly-discriminator": {
                            "propertyName": "author_type",
                            "mapping": {
                                "user": "#/components/schemas/UserSummary"
                            }
                        },
                        "x-plnnrly-morph-aliases": [
                            "user"
                        ],
                        "description": "Who wrote the comment. Renders as `{\"id\": N}` unless `?include=author` expands it, which additionally requires the users prefix's read grant and a token covering it."
                    },
                    "commentable_type": {
                        "type": "string",
                        "enum": [
                            "work_item",
                            "drive_item"
                        ],
                        "x-extensible-enum": true,
                        "description": "The kind of record `commentable` points at; it says which branch of `commentable` applies."
                    },
                    "commentable_id": {
                        "type": "integer",
                        "description": "The id of the record `commentable` points at."
                    },
                    "commentable": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            }
                        ],
                        "x-plnnrly-discriminator": {
                            "propertyName": "commentable_type",
                            "mapping": []
                        },
                        "x-plnnrly-morph-aliases": [
                            "work_item",
                            "drive_item"
                        ],
                        "description": "The record the comment hangs off, as a reference. Look it up from `commentable_type` and `commentable_id` with `GET /api/v2/work-items/{id}` or GET `/api/v2/drive-items/{id}`. It is not expandable: `?include=commentable` is a `400`."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "When the comment was posted."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync, and as the revision the strong `ETag` is derived from."
                    },
                    "links": {
                        "$ref": "#/components/schemas/CommentLinks"
                    }
                },
                "required": [
                    "id",
                    "body",
                    "author_type",
                    "author_id",
                    "author",
                    "commentable_type",
                    "commentable_id",
                    "commentable",
                    "created_at",
                    "updated_at",
                    "links"
                ],
                "description": "A comment on a work item or on a drive item: the rich-text body one user posted to a thread, with its author and its timestamps. Comments are reached through their parent (GET `work-items/{work_item}/comments`) or by id on the flat item path, and the parent is named by the `commentable_type` / `commentable_id` pair rather than by an expandable relation. Editing and deleting are author-only."
            },
            "CommentLinks": {
                "type": "object",
                "properties": {
                    "self": {
                        "type": "string",
                        "format": "uri",
                        "description": "The canonical URI of this record."
                    }
                },
                "required": [
                    "self"
                ],
                "additionalProperties": false
            },
            "CommentList": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "body": {
                        "type": "string",
                        "format": "html",
                        "description": "Comment body as rich text. HTML from the plnnrly editor, sanitised on the way out, so what you read back can differ from the bytes you sent. Mention spans (`<span data-type=\"mention\" data-id=\"3\">`) survive untouched and may be posted back verbatim; constructing a new mention is not supported in v2.0."
                    },
                    "author_type": {
                        "type": "string",
                        "enum": [
                            "user"
                        ],
                        "x-extensible-enum": true,
                        "description": "The kind of record `author` points at; it says which branch of `author` applies."
                    },
                    "author_id": {
                        "type": "integer",
                        "description": "The id of the record `author` points at."
                    },
                    "author": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/UserSummary"
                            }
                        ],
                        "x-plnnrly-discriminator": {
                            "propertyName": "author_type",
                            "mapping": {
                                "user": "#/components/schemas/UserSummary"
                            }
                        },
                        "x-plnnrly-morph-aliases": [
                            "user"
                        ],
                        "description": "Who wrote the comment. Renders as `{\"id\": N}` unless `?include=author` expands it, which additionally requires the users prefix's read grant and a token covering it."
                    },
                    "commentable_type": {
                        "type": "string",
                        "enum": [
                            "work_item",
                            "drive_item"
                        ],
                        "x-extensible-enum": true,
                        "description": "The kind of record `commentable` points at; it says which branch of `commentable` applies."
                    },
                    "commentable_id": {
                        "type": "integer",
                        "description": "The id of the record `commentable` points at."
                    },
                    "commentable": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            }
                        ],
                        "x-plnnrly-discriminator": {
                            "propertyName": "commentable_type",
                            "mapping": []
                        },
                        "x-plnnrly-morph-aliases": [
                            "work_item",
                            "drive_item"
                        ],
                        "description": "The record the comment hangs off, as a reference. Look it up from `commentable_type` and `commentable_id` with `GET /api/v2/work-items/{id}` or GET `/api/v2/drive-items/{id}`. It is not expandable: `?include=commentable` is a `400`."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "When the comment was posted."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync, and as the revision the strong `ETag` is derived from."
                    },
                    "links": {
                        "$ref": "#/components/schemas/CommentLinks"
                    }
                },
                "required": [
                    "id",
                    "body",
                    "author_type",
                    "author_id",
                    "author",
                    "commentable_type",
                    "commentable_id",
                    "commentable",
                    "created_at",
                    "updated_at",
                    "links"
                ],
                "description": "A comment on a work item or on a drive item: the rich-text body one user posted to a thread, with its author and its timestamps. Comments are reached through their parent (GET `work-items/{work_item}/comments`) or by id on the flat item path, and the parent is named by the `commentable_type` / `commentable_id` pair rather than by an expandable relation. Editing and deleting are author-only."
            },
            "CommentSummary": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "author_type": {
                        "type": "string",
                        "enum": [
                            "user"
                        ],
                        "x-extensible-enum": true,
                        "description": "The kind of record `author` points at; it says which branch of `author` applies."
                    },
                    "author_id": {
                        "type": "integer",
                        "description": "The id of the record `author` points at."
                    },
                    "author": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/UserSummary"
                            }
                        ],
                        "x-plnnrly-discriminator": {
                            "propertyName": "author_type",
                            "mapping": {
                                "user": "#/components/schemas/UserSummary"
                            }
                        },
                        "x-plnnrly-morph-aliases": [
                            "user"
                        ],
                        "description": "Who wrote the comment."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "When the comment was posted."
                    }
                },
                "required": [
                    "id",
                    "author_type",
                    "author_id",
                    "author",
                    "created_at"
                ],
                "description": "A comment, as referenced from another resource."
            },
            "CommentSummaryInclude": {
                "type": "object",
                "properties": {
                    "data": {
                        "type": "array",
                        "items": {
                            "$ref": "#/components/schemas/CommentSummary"
                        },
                        "maxItems": 25
                    },
                    "meta": {
                        "type": "object",
                        "properties": {
                            "count": {
                                "type": "integer",
                                "minimum": 0
                            },
                            "truncated": {
                                "type": "boolean"
                            }
                        },
                        "required": [
                            "truncated"
                        ],
                        "additionalProperties": false
                    }
                },
                "required": [
                    "data",
                    "meta"
                ],
                "additionalProperties": false
            },
            "CompanyBranch": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Branch name as shown in the UI, e.g. Rotterdam."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "customers_count": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Number of customers assigned to this branch."
                    },
                    "links": {
                        "$ref": "#/components/schemas/CompanyBranchLinks"
                    }
                },
                "required": [
                    "id",
                    "name",
                    "created_at",
                    "updated_at",
                    "customers_count",
                    "links"
                ],
                "description": "A company branch: one of the organisation's own locations, used to group the customers a branch is responsible for. Deleting one un-assigns its customers rather than blocking."
            },
            "CompanyBranchLinks": {
                "type": "object",
                "properties": {
                    "self": {
                        "type": "string",
                        "format": "uri",
                        "description": "The canonical URI of this record."
                    },
                    "customers": {
                        "type": "string",
                        "format": "uri",
                        "description": "Customers assigned to this branch."
                    }
                },
                "required": [
                    "self",
                    "customers"
                ],
                "additionalProperties": false
            },
            "CompanyBranchList": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Branch name as shown in the UI, e.g. Rotterdam."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "customers_count": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Number of customers assigned to this branch."
                    },
                    "links": {
                        "$ref": "#/components/schemas/CompanyBranchLinks"
                    }
                },
                "required": [
                    "id",
                    "name",
                    "created_at",
                    "updated_at",
                    "customers_count",
                    "links"
                ],
                "description": "A company branch: one of the organisation's own locations, used to group the customers a branch is responsible for. Deleting one un-assigns its customers rather than blocking."
            },
            "CompanyBranchSummary": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Branch name."
                    }
                },
                "required": [
                    "id",
                    "name"
                ],
                "description": "A company branch, as referenced from another resource."
            },
            "CompanyBranchesIndexMeta": {
                "type": "object",
                "properties": {
                    "page": {
                        "$ref": "#/components/schemas/Page"
                    }
                },
                "additionalProperties": false,
                "required": [
                    "page"
                ]
            },
            "Contact": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "contactable_type": {
                        "type": "string",
                        "enum": [
                            "customer",
                            "user"
                        ],
                        "x-extensible-enum": true,
                        "description": "The kind of record `contactable` points at; it says which branch of `contactable` applies."
                    },
                    "contactable_id": {
                        "type": "integer",
                        "description": "The id of the record `contactable` points at."
                    },
                    "contactable": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/CustomerSummary"
                            },
                            {
                                "$ref": "#/components/schemas/UserSummary"
                            }
                        ],
                        "x-plnnrly-discriminator": {
                            "propertyName": "contactable_type",
                            "mapping": {
                                "customer": "#/components/schemas/CustomerSummary",
                                "user": "#/components/schemas/UserSummary"
                            }
                        },
                        "x-plnnrly-morph-aliases": [
                            "customer",
                            "user"
                        ],
                        "description": "The record this contact belongs to: a customer for a CRM contact, a user for an employee's emergency contact. Rendered as {\"id\": N} until named in `?include=contactable`, and still as {\"id\": N} where the caller may not read the parent's prefix. `contactable_type` and `contactable_id` are always present, so the parent can be found without the include, and the reference form shows nothing `contactable_id` does not."
                    },
                    "contact_role_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Assigned contact role, or `null`. Deleting a role unassigns it rather than refusing."
                    },
                    "role": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/ContactRoleSummary"
                            },
                            {
                                "type": "null"
                            }
                        ],
                        "description": "The contact's role. Rendered as {\"id\": N} until named in `?include=role`, and `null` where no role is assigned."
                    },
                    "name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Contact name. This is the default sort key and one of the fields `?q=` searches."
                    },
                    "email": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Email address. The second field `?q=` searches; not unique."
                    },
                    "phone": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Phone number."
                    },
                    "address": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Street address. plnnrly pre-fills this from the customer when adding a contact, but the API fills in no default for it."
                    },
                    "zip": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Postal code."
                    },
                    "city": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "City."
                    },
                    "country": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Country as an ISO 3166-1 alpha-2 code, e.g. NL, as plnnrly writes it. Older records may hold any string."
                    },
                    "position": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Display order within one parent, ascending. Null where unset."
                    },
                    "comment": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "html",
                        "description": "Free-form note, sanitised HTML. The emergency-contact form uses it for the relationship to the employee, e.g. Partner."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "links": {
                        "$ref": "#/components/schemas/ContactLinks"
                    }
                },
                "required": [
                    "id",
                    "contactable_type",
                    "contactable_id",
                    "contactable",
                    "contact_role_id",
                    "role",
                    "name",
                    "email",
                    "phone",
                    "address",
                    "zip",
                    "city",
                    "country",
                    "position",
                    "comment",
                    "created_at",
                    "updated_at",
                    "links"
                ],
                "description": "A contact person: either a customer's contact (the CRM address book) or an employee's emergency contact, distinguished by `contactable_type`. Emergency contacts are personal data: they are visible and writable only to the employee they belong to, whatever the customers permission. Contacts are not soft-deleted, so deleting one is permanent."
            },
            "ContactLinks": {
                "type": "object",
                "properties": {
                    "self": {
                        "type": "string",
                        "format": "uri",
                        "description": "The canonical URI of this record."
                    }
                },
                "required": [
                    "self"
                ],
                "additionalProperties": false
            },
            "ContactList": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "contactable_type": {
                        "type": "string",
                        "enum": [
                            "customer",
                            "user"
                        ],
                        "x-extensible-enum": true,
                        "description": "The kind of record `contactable` points at; it says which branch of `contactable` applies."
                    },
                    "contactable_id": {
                        "type": "integer",
                        "description": "The id of the record `contactable` points at."
                    },
                    "contactable": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/CustomerSummary"
                            },
                            {
                                "$ref": "#/components/schemas/UserSummary"
                            }
                        ],
                        "x-plnnrly-discriminator": {
                            "propertyName": "contactable_type",
                            "mapping": {
                                "customer": "#/components/schemas/CustomerSummary",
                                "user": "#/components/schemas/UserSummary"
                            }
                        },
                        "x-plnnrly-morph-aliases": [
                            "customer",
                            "user"
                        ],
                        "description": "The record this contact belongs to: a customer for a CRM contact, a user for an employee's emergency contact. Rendered as {\"id\": N} until named in `?include=contactable`, and still as {\"id\": N} where the caller may not read the parent's prefix. `contactable_type` and `contactable_id` are always present, so the parent can be found without the include, and the reference form shows nothing `contactable_id` does not."
                    },
                    "contact_role_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Assigned contact role, or `null`. Deleting a role unassigns it rather than refusing."
                    },
                    "role": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/ContactRoleSummary"
                            },
                            {
                                "type": "null"
                            }
                        ],
                        "description": "The contact's role. Rendered as {\"id\": N} until named in `?include=role`, and `null` where no role is assigned."
                    },
                    "name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Contact name. This is the default sort key and one of the fields `?q=` searches."
                    },
                    "email": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Email address. The second field `?q=` searches; not unique."
                    },
                    "phone": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Phone number."
                    },
                    "address": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Street address. plnnrly pre-fills this from the customer when adding a contact, but the API fills in no default for it."
                    },
                    "zip": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Postal code."
                    },
                    "city": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "City."
                    },
                    "country": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Country as an ISO 3166-1 alpha-2 code, e.g. NL, as plnnrly writes it. Older records may hold any string."
                    },
                    "position": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Display order within one parent, ascending. Null where unset."
                    },
                    "comment": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "html",
                        "description": "Free-form note, sanitised HTML. The emergency-contact form uses it for the relationship to the employee, e.g. Partner."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "links": {
                        "$ref": "#/components/schemas/ContactLinks"
                    }
                },
                "required": [
                    "id",
                    "contactable_type",
                    "contactable_id",
                    "contactable",
                    "contact_role_id",
                    "role",
                    "name",
                    "email",
                    "phone",
                    "address",
                    "zip",
                    "city",
                    "country",
                    "position",
                    "comment",
                    "created_at",
                    "updated_at",
                    "links"
                ],
                "description": "A contact person: either a customer's contact (the CRM address book) or an employee's emergency contact, distinguished by `contactable_type`. Emergency contacts are personal data: they are visible and writable only to the employee they belong to, whatever the customers permission. Contacts are not soft-deleted, so deleting one is permanent."
            },
            "ContactRole": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Role name as shown in the UI, unique across the organisation. This is the default sort key and the field `?q=` searches."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "contacts_count": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Number of contacts assigned this role."
                    },
                    "links": {
                        "$ref": "#/components/schemas/ContactRoleLinks"
                    }
                },
                "required": [
                    "id",
                    "name",
                    "created_at",
                    "updated_at",
                    "contacts_count",
                    "links"
                ],
                "description": "A contact role: the organisation-wide list a contact's function is picked from, e.g. Technical contact or Invoicing. Deleting one unassigns it from every contact rather than refusing."
            },
            "ContactRoleLinks": {
                "type": "object",
                "properties": {
                    "self": {
                        "type": "string",
                        "format": "uri",
                        "description": "The canonical URI of this record."
                    },
                    "contacts": {
                        "type": "string",
                        "format": "uri",
                        "description": "Contacts assigned this role."
                    }
                },
                "required": [
                    "self",
                    "contacts"
                ],
                "additionalProperties": false
            },
            "ContactRoleList": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Role name as shown in the UI, unique across the organisation. This is the default sort key and the field `?q=` searches."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "contacts_count": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Number of contacts assigned this role."
                    },
                    "links": {
                        "$ref": "#/components/schemas/ContactRoleLinks"
                    }
                },
                "required": [
                    "id",
                    "name",
                    "created_at",
                    "updated_at",
                    "contacts_count",
                    "links"
                ],
                "description": "A contact role: the organisation-wide list a contact's function is picked from, e.g. Technical contact or Invoicing. Deleting one unassigns it from every contact rather than refusing."
            },
            "ContactRoleSummary": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Role name as shown in the UI."
                    }
                },
                "required": [
                    "id",
                    "name"
                ],
                "description": "A contact role, as referenced from another resource."
            },
            "ContactRolesIndexMeta": {
                "type": "object",
                "properties": {
                    "page": {
                        "$ref": "#/components/schemas/Page"
                    }
                },
                "additionalProperties": false,
                "required": [
                    "page"
                ]
            },
            "ContactsIndexMeta": {
                "type": "object",
                "properties": {
                    "page": {
                        "$ref": "#/components/schemas/Page"
                    }
                },
                "additionalProperties": false,
                "required": [
                    "page"
                ]
            },
            "Customer": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Customer name. This is the default sort key and one of the fields `?q=` searches."
                    },
                    "code": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Short customer code, e.g. ACM. Prefixes every work item's issue key, is enforced unique on write, and is one of the fields `?q=` searches."
                    },
                    "color": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Chip colour as a CSS hex triplet or sextet, e.g. #1d4ed8. A stored value that is not valid hex reads back as #6b7280."
                    },
                    "status": {
                        "type": "string",
                        "enum": [
                            "suspect",
                            "prospect",
                            "future-opportunity",
                            "active",
                            "inactive"
                        ],
                        "x-extensible-enum": true,
                        "description": "Where the customer sits in the sales lifecycle."
                    },
                    "email": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Primary email address. The third field `?q=` searches; not unique."
                    },
                    "phone": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Phone number."
                    },
                    "address": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Street address. Writing this, zip or city re-geocodes lat and lng."
                    },
                    "zip": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Postal code."
                    },
                    "city": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "City."
                    },
                    "country": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Country as an ISO 3166-1 alpha-2 code, e.g. NL, as plnnrly writes it. Older records may hold any string."
                    },
                    "lat": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Latitude, geocoded from the address whenever address, zip or city changes. Not writable.",
                        "x-plnnrly-server-assigned": true
                    },
                    "lng": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Longitude, geocoded from the address whenever address, zip or city changes. Not writable.",
                        "x-plnnrly-server-assigned": true
                    },
                    "founded_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date",
                        "description": "Day the customer's company was founded."
                    },
                    "customer_since": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date",
                        "description": "Day this customer became a customer."
                    },
                    "is_vat_exempt": {
                        "type": "boolean",
                        "description": "Whether invoices to this customer are issued without VAT."
                    },
                    "payment_days": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Payment term in days, as held by the accounting integration. Read by the invoice composer; written only by the Exact sync.",
                        "x-plnnrly-server-assigned": true
                    },
                    "debtor_code": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Debtor number in the accounting system. Writing it requires `billing_settings::update`."
                    },
                    "exact_account_id": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Account identifier in Exact Online, written by the sync.",
                        "x-plnnrly-server-assigned": true
                    },
                    "exact_synced_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "When the Exact sync last handled this customer, or `null` if never.",
                        "x-plnnrly-server-assigned": true
                    },
                    "exact_keep_local_address": {
                        "type": "boolean",
                        "description": "Whether the Exact sync leaves this customer's local address alone although it differs from the one in Exact. Set when a user accepts the difference on the Exact customers screen in plnnrly; not writable.",
                        "x-plnnrly-server-assigned": true
                    },
                    "account_manager_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Responsible account manager. Required on create; unassigned when the user is deleted."
                    },
                    "customer_category_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Customer category, or `null`."
                    },
                    "company_type_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Company type, or `null`. Company types are your organisation's own list, maintained in plnnrly and not exposed as a collection."
                    },
                    "company_size_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Company size band, or `null`. Company sizes are your organisation's own list, maintained in plnnrly and not exposed as a collection."
                    },
                    "company_branch_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Industry branch, or `null`."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "deleted_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "Soft-delete timestamp, `null` while live."
                    },
                    "customer_category": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/CustomerCategorySummary"
                            },
                            {
                                "type": "null"
                            }
                        ],
                        "description": "The customer's category. Rendered as {\"id\": N} until named in `?include=customer_category`, and `null` where none is assigned."
                    },
                    "company_branch": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/CompanyBranchSummary"
                            },
                            {
                                "type": "null"
                            }
                        ],
                        "description": "The customer's industry branch. Rendered as {\"id\": N} until named in `?include=company_branch`, and `null` where none is assigned."
                    },
                    "tags": {
                        "$ref": "#/components/schemas/TagSummaryInclude",
                        "description": "Tags on this customer. A customer takes at most 50 tags, so the include lists them all. `tag_ids` replaces the whole set.",
                        "x-plnnrly-include-only": true
                    },
                    "projects_count": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Number of live projects for this customer. Soft-deleted projects are not counted."
                    },
                    "contacts_count": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Number of contacts on this customer."
                    },
                    "tags_count": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Number of tags on this customer."
                    },
                    "links": {
                        "$ref": "#/components/schemas/CustomerLinks"
                    }
                },
                "required": [
                    "id",
                    "name",
                    "code",
                    "color",
                    "status",
                    "email",
                    "phone",
                    "address",
                    "zip",
                    "city",
                    "country",
                    "lat",
                    "lng",
                    "founded_at",
                    "customer_since",
                    "is_vat_exempt",
                    "payment_days",
                    "debtor_code",
                    "exact_account_id",
                    "exact_synced_at",
                    "exact_keep_local_address",
                    "account_manager_id",
                    "customer_category_id",
                    "company_type_id",
                    "company_size_id",
                    "company_branch_id",
                    "created_at",
                    "updated_at",
                    "deleted_at",
                    "customer_category",
                    "company_branch",
                    "projects_count",
                    "contacts_count",
                    "tags_count",
                    "links"
                ],
                "description": "A customer: the CRM record every project, contact, subscription and sales invoice hangs off. Soft-deleted, and deleting one cascades to its projects.",
                "x-plnnrly-omitted": {
                    "exact_sync_issues": "Holds free-form sync messages with no stable shape, so it has no honest published type. exact_synced_at is published instead, so a client can still tell whether a sync has run.",
                    "location": "Not stored on its own: it combines lat and lng, both of which are published as their own read-only fields."
                }
            },
            "CustomerCategoriesIndexMeta": {
                "type": "object",
                "properties": {
                    "page": {
                        "$ref": "#/components/schemas/Page"
                    }
                },
                "additionalProperties": false,
                "required": [
                    "page"
                ]
            },
            "CustomerCategory": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Category name as shown in the UI, e.g. Enterprise."
                    },
                    "description": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "x-plnnrly-multiline": true,
                        "description": "Free-text note on what belongs in this category."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "customers_count": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Number of customers in this category."
                    },
                    "links": {
                        "$ref": "#/components/schemas/CustomerCategoryLinks"
                    }
                },
                "required": [
                    "id",
                    "name",
                    "description",
                    "created_at",
                    "updated_at",
                    "customers_count",
                    "links"
                ],
                "description": "A customer category: the hand-maintained taxonomy customers are grouped by, used for segmentation and reporting. Deleting one un-categorises its customers rather than blocking."
            },
            "CustomerCategoryLinks": {
                "type": "object",
                "properties": {
                    "self": {
                        "type": "string",
                        "format": "uri",
                        "description": "The canonical URI of this record."
                    },
                    "customers": {
                        "type": "string",
                        "format": "uri",
                        "description": "Customers in this category."
                    }
                },
                "required": [
                    "self",
                    "customers"
                ],
                "additionalProperties": false
            },
            "CustomerCategoryList": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Category name as shown in the UI, e.g. Enterprise."
                    },
                    "description": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "x-plnnrly-multiline": true,
                        "description": "Free-text note on what belongs in this category."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "customers_count": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Number of customers in this category."
                    },
                    "links": {
                        "$ref": "#/components/schemas/CustomerCategoryLinks"
                    }
                },
                "required": [
                    "id",
                    "name",
                    "description",
                    "created_at",
                    "updated_at",
                    "customers_count",
                    "links"
                ],
                "description": "A customer category: the hand-maintained taxonomy customers are grouped by, used for segmentation and reporting. Deleting one un-categorises its customers rather than blocking."
            },
            "CustomerCategorySummary": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Category name."
                    }
                },
                "required": [
                    "id",
                    "name"
                ],
                "description": "A customer category, as referenced from another resource."
            },
            "CustomerLinks": {
                "type": "object",
                "properties": {
                    "self": {
                        "type": "string",
                        "format": "uri",
                        "description": "The canonical URI of this record."
                    },
                    "projects": {
                        "type": "string",
                        "format": "uri",
                        "description": "Projects for this customer."
                    },
                    "contacts": {
                        "type": "string",
                        "format": "uri",
                        "description": "Contacts on this customer."
                    }
                },
                "required": [
                    "self",
                    "projects",
                    "contacts"
                ],
                "additionalProperties": false
            },
            "CustomerList": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Customer name. This is the default sort key and one of the fields `?q=` searches."
                    },
                    "code": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Short customer code, e.g. ACM. Prefixes every work item's issue key, is enforced unique on write, and is one of the fields `?q=` searches."
                    },
                    "color": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Chip colour as a CSS hex triplet or sextet, e.g. #1d4ed8. A stored value that is not valid hex reads back as #6b7280."
                    },
                    "status": {
                        "type": "string",
                        "enum": [
                            "suspect",
                            "prospect",
                            "future-opportunity",
                            "active",
                            "inactive"
                        ],
                        "x-extensible-enum": true,
                        "description": "Where the customer sits in the sales lifecycle."
                    },
                    "email": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Primary email address. The third field `?q=` searches; not unique."
                    },
                    "phone": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Phone number."
                    },
                    "address": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Street address. Writing this, zip or city re-geocodes lat and lng."
                    },
                    "zip": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Postal code."
                    },
                    "city": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "City."
                    },
                    "country": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Country as an ISO 3166-1 alpha-2 code, e.g. NL, as plnnrly writes it. Older records may hold any string."
                    },
                    "lat": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Latitude, geocoded from the address whenever address, zip or city changes. Not writable.",
                        "x-plnnrly-server-assigned": true
                    },
                    "lng": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Longitude, geocoded from the address whenever address, zip or city changes. Not writable.",
                        "x-plnnrly-server-assigned": true
                    },
                    "founded_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date",
                        "description": "Day the customer's company was founded."
                    },
                    "customer_since": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date",
                        "description": "Day this customer became a customer."
                    },
                    "is_vat_exempt": {
                        "type": "boolean",
                        "description": "Whether invoices to this customer are issued without VAT."
                    },
                    "payment_days": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Payment term in days, as held by the accounting integration. Read by the invoice composer; written only by the Exact sync.",
                        "x-plnnrly-server-assigned": true
                    },
                    "debtor_code": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Debtor number in the accounting system. Writing it requires `billing_settings::update`."
                    },
                    "exact_account_id": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Account identifier in Exact Online, written by the sync.",
                        "x-plnnrly-server-assigned": true
                    },
                    "exact_synced_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "When the Exact sync last handled this customer, or `null` if never.",
                        "x-plnnrly-server-assigned": true
                    },
                    "exact_keep_local_address": {
                        "type": "boolean",
                        "description": "Whether the Exact sync leaves this customer's local address alone although it differs from the one in Exact. Set when a user accepts the difference on the Exact customers screen in plnnrly; not writable.",
                        "x-plnnrly-server-assigned": true
                    },
                    "account_manager_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Responsible account manager. Required on create; unassigned when the user is deleted."
                    },
                    "customer_category_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Customer category, or `null`."
                    },
                    "company_type_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Company type, or `null`. Company types are your organisation's own list, maintained in plnnrly and not exposed as a collection."
                    },
                    "company_size_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Company size band, or `null`. Company sizes are your organisation's own list, maintained in plnnrly and not exposed as a collection."
                    },
                    "company_branch_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Industry branch, or `null`."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "deleted_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "Soft-delete timestamp, `null` while live."
                    },
                    "customer_category": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/CustomerCategorySummary"
                            },
                            {
                                "type": "null"
                            }
                        ],
                        "description": "The customer's category. Rendered as {\"id\": N} until named in `?include=customer_category`, and `null` where none is assigned."
                    },
                    "company_branch": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/CompanyBranchSummary"
                            },
                            {
                                "type": "null"
                            }
                        ],
                        "description": "The customer's industry branch. Rendered as {\"id\": N} until named in `?include=company_branch`, and `null` where none is assigned."
                    },
                    "tags": {
                        "$ref": "#/components/schemas/TagSummaryInclude",
                        "description": "Tags on this customer. A customer takes at most 50 tags, so the include lists them all. `tag_ids` replaces the whole set.",
                        "x-plnnrly-include-only": true
                    },
                    "projects_count": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Number of live projects for this customer. Soft-deleted projects are not counted."
                    },
                    "contacts_count": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Number of contacts on this customer."
                    },
                    "tags_count": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Number of tags on this customer."
                    },
                    "links": {
                        "$ref": "#/components/schemas/CustomerLinks"
                    }
                },
                "required": [
                    "id",
                    "name",
                    "code",
                    "color",
                    "status",
                    "email",
                    "phone",
                    "address",
                    "zip",
                    "city",
                    "country",
                    "lat",
                    "lng",
                    "founded_at",
                    "customer_since",
                    "is_vat_exempt",
                    "payment_days",
                    "debtor_code",
                    "exact_account_id",
                    "exact_synced_at",
                    "exact_keep_local_address",
                    "account_manager_id",
                    "customer_category_id",
                    "company_type_id",
                    "company_size_id",
                    "company_branch_id",
                    "created_at",
                    "updated_at",
                    "deleted_at",
                    "customer_category",
                    "company_branch",
                    "projects_count",
                    "contacts_count",
                    "tags_count",
                    "links"
                ],
                "description": "A customer: the CRM record every project, contact, subscription and sales invoice hangs off. Soft-deleted, and deleting one cascades to its projects."
            },
            "CustomerSubscription": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "customer_id": {
                        "type": "integer",
                        "description": "Customer this subscription belongs to."
                    },
                    "customer": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/CustomerSummary"
                            }
                        ],
                        "description": "The customer this subscription belongs to. Expanding it with `?include=customer` additionally requires the `customers::read` permission and a token scope covering the customers prefix."
                    },
                    "subscription_type_id": {
                        "type": "integer",
                        "description": "Subscription type this subscription is an instance of."
                    },
                    "subscription_type": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/SubscriptionTypeSummary"
                            }
                        ],
                        "description": "The subscription type this subscription is an instance of, which is also where its displayed name comes from. Expanding it with `?include=subscription_type` additionally requires the `subscription_types::read` permission and a token scope covering the `subscription_types` prefix."
                    },
                    "description": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "x-plnnrly-multiline": true,
                        "description": "What this subscription covers, as free text. This is the field `?q=` searches, and it is also what a generated invoice line falls back to."
                    },
                    "status": {
                        "type": "string",
                        "enum": [
                            "pending",
                            "active",
                            "cancelled",
                            "expired"
                        ],
                        "x-extensible-enum": true,
                        "description": "Lifecycle status. Only an active subscription is picked up by the renewal run; the other three are inert."
                    },
                    "billing_interval": {
                        "type": "string",
                        "enum": [
                            "yearly",
                            "monthly",
                            "one-time"
                        ],
                        "x-extensible-enum": true,
                        "description": "How often the subscription is invoiced. one-time forces `auto_renew` to `false`, because a one-off has no next period to move on to."
                    },
                    "starts_at": {
                        "type": "string",
                        "format": "date",
                        "description": "First day the subscription runs from, as a zone-free calendar date. Newest first is the default sort."
                    },
                    "next_invoice_date": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date",
                        "description": "First day of the period to be invoiced next. Omit it on create and the server fills it with `starts_at`, so the first period is billable; the renewal run advances it by one interval each time it invoices. Null makes the subscription invisible to that run (it is skipped, silently), so a subscription that should never auto-invoice is expressed this way."
                    },
                    "cancelled_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date",
                        "description": "Day the subscription was cancelled, `null` while it has not been. It is a record of the cancellation date only: nothing derives status from it."
                    },
                    "expires_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date",
                        "description": "Day the subscription runs out, `null` when it is open-ended."
                    },
                    "auto_renew": {
                        "type": "boolean",
                        "description": "Whether the renewal run may invoice this subscription again. A one-time subscription is always `false`: it is forced on every save, so sending `true` together with `billing_interval=one-time` succeeds and comes back as `false` rather than failing."
                    },
                    "exact_subscription_id": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Exact Online subscription this one maps to, by Exact's own external identifier, not by a local id. Written by the sync; `null` when the subscription has never been synced.",
                        "x-plnnrly-server-assigned": true
                    },
                    "last_invoiced_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date",
                        "description": "Day the subscription was last invoiced, `null` until it has been. Maintained by the renewal run.",
                        "x-plnnrly-server-assigned": true
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync. It also feeds the `ETag` revision, because a customer subscription has no `version` field."
                    },
                    "deleted_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "Soft-delete timestamp, `null` while live."
                    },
                    "items_count": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Number of lines on this subscription."
                    },
                    "links": {
                        "$ref": "#/components/schemas/CustomerSubscriptionLinks"
                    }
                },
                "required": [
                    "id",
                    "customer_id",
                    "customer",
                    "subscription_type_id",
                    "subscription_type",
                    "description",
                    "status",
                    "billing_interval",
                    "starts_at",
                    "next_invoice_date",
                    "cancelled_at",
                    "expires_at",
                    "auto_renew",
                    "exact_subscription_id",
                    "last_invoiced_at",
                    "created_at",
                    "updated_at",
                    "deleted_at",
                    "items_count",
                    "links"
                ],
                "description": "A recurring agreement with one customer: what it covers, the interval it is billed at, when it started and when it is next invoiced. Its lines live on the items sub-resource, and the renewal run turns a due subscription into a sales invoice. A subscription with no next invoice date is never invoiced automatically."
            },
            "CustomerSubscriptionLinks": {
                "type": "object",
                "properties": {
                    "self": {
                        "type": "string",
                        "format": "uri",
                        "description": "The canonical URI of this record."
                    },
                    "items": {
                        "type": "string",
                        "format": "uri",
                        "description": "Lines on this subscription."
                    }
                },
                "required": [
                    "self",
                    "items"
                ],
                "additionalProperties": false
            },
            "CustomerSubscriptionList": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "customer_id": {
                        "type": "integer",
                        "description": "Customer this subscription belongs to."
                    },
                    "customer": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/CustomerSummary"
                            }
                        ],
                        "description": "The customer this subscription belongs to. Expanding it with `?include=customer` additionally requires the `customers::read` permission and a token scope covering the customers prefix."
                    },
                    "subscription_type_id": {
                        "type": "integer",
                        "description": "Subscription type this subscription is an instance of."
                    },
                    "subscription_type": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/SubscriptionTypeSummary"
                            }
                        ],
                        "description": "The subscription type this subscription is an instance of, which is also where its displayed name comes from. Expanding it with `?include=subscription_type` additionally requires the `subscription_types::read` permission and a token scope covering the `subscription_types` prefix."
                    },
                    "description": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "x-plnnrly-multiline": true,
                        "description": "What this subscription covers, as free text. This is the field `?q=` searches, and it is also what a generated invoice line falls back to."
                    },
                    "status": {
                        "type": "string",
                        "enum": [
                            "pending",
                            "active",
                            "cancelled",
                            "expired"
                        ],
                        "x-extensible-enum": true,
                        "description": "Lifecycle status. Only an active subscription is picked up by the renewal run; the other three are inert."
                    },
                    "billing_interval": {
                        "type": "string",
                        "enum": [
                            "yearly",
                            "monthly",
                            "one-time"
                        ],
                        "x-extensible-enum": true,
                        "description": "How often the subscription is invoiced. one-time forces `auto_renew` to `false`, because a one-off has no next period to move on to."
                    },
                    "starts_at": {
                        "type": "string",
                        "format": "date",
                        "description": "First day the subscription runs from, as a zone-free calendar date. Newest first is the default sort."
                    },
                    "next_invoice_date": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date",
                        "description": "First day of the period to be invoiced next. Omit it on create and the server fills it with `starts_at`, so the first period is billable; the renewal run advances it by one interval each time it invoices. Null makes the subscription invisible to that run (it is skipped, silently), so a subscription that should never auto-invoice is expressed this way."
                    },
                    "cancelled_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date",
                        "description": "Day the subscription was cancelled, `null` while it has not been. It is a record of the cancellation date only: nothing derives status from it."
                    },
                    "expires_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date",
                        "description": "Day the subscription runs out, `null` when it is open-ended."
                    },
                    "auto_renew": {
                        "type": "boolean",
                        "description": "Whether the renewal run may invoice this subscription again. A one-time subscription is always `false`: it is forced on every save, so sending `true` together with `billing_interval=one-time` succeeds and comes back as `false` rather than failing."
                    },
                    "exact_subscription_id": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Exact Online subscription this one maps to, by Exact's own external identifier, not by a local id. Written by the sync; `null` when the subscription has never been synced.",
                        "x-plnnrly-server-assigned": true
                    },
                    "last_invoiced_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date",
                        "description": "Day the subscription was last invoiced, `null` until it has been. Maintained by the renewal run.",
                        "x-plnnrly-server-assigned": true
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync. It also feeds the `ETag` revision, because a customer subscription has no `version` field."
                    },
                    "deleted_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "Soft-delete timestamp, `null` while live."
                    },
                    "items_count": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Number of lines on this subscription."
                    },
                    "links": {
                        "$ref": "#/components/schemas/CustomerSubscriptionLinks"
                    }
                },
                "required": [
                    "id",
                    "customer_id",
                    "customer",
                    "subscription_type_id",
                    "subscription_type",
                    "description",
                    "status",
                    "billing_interval",
                    "starts_at",
                    "next_invoice_date",
                    "cancelled_at",
                    "expires_at",
                    "auto_renew",
                    "exact_subscription_id",
                    "last_invoiced_at",
                    "created_at",
                    "updated_at",
                    "deleted_at",
                    "items_count",
                    "links"
                ],
                "description": "A recurring agreement with one customer: what it covers, the interval it is billed at, when it started and when it is next invoiced. Its lines live on the items sub-resource, and the renewal run turns a due subscription into a sales invoice. A subscription with no next invoice date is never invoiced automatically."
            },
            "CustomerSubscriptionSummary": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "starts_at": {
                        "type": "string",
                        "format": "date",
                        "description": "First day the subscription runs from."
                    },
                    "customer_id": {
                        "type": "integer",
                        "description": "Customer the subscription belongs to."
                    }
                },
                "required": [
                    "id",
                    "starts_at",
                    "customer_id"
                ],
                "description": "A customer subscription, as referenced from another resource."
            },
            "CustomerSubscriptionsActionsRestoreMeta": {
                "type": "object",
                "properties": {
                    "warnings": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "code": {
                                    "type": "string",
                                    "enum": [
                                        "already_in_target_state"
                                    ],
                                    "x-extensible-enum": true,
                                    "description": "Machine-readable warning code. Switch on this, never on detail."
                                },
                                "detail": {
                                    "type": "string",
                                    "description": "Human-readable explanation."
                                },
                                "pointer": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "description": "RFC 6901 pointer into the request body, where the warning is about a body field."
                                },
                                "parameter": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "description": "The query-parameter name, brackets included, where the warning is about a parameter."
                                },
                                "sunset": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "format": "date",
                                    "description": "Date the deprecated thing stops working."
                                },
                                "replacement": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "description": "What to use instead."
                                }
                            },
                            "required": [
                                "code",
                                "detail"
                            ],
                            "description": "An advisory about this request. It never changes the status code or `data`."
                        },
                        "description": "Non-fatal notes about this request. Switch on `code`, never on `detail`."
                    }
                },
                "additionalProperties": false
            },
            "CustomerSubscriptionsIndexMeta": {
                "type": "object",
                "properties": {
                    "page": {
                        "$ref": "#/components/schemas/Page"
                    }
                },
                "additionalProperties": false,
                "required": [
                    "page"
                ]
            },
            "CustomerSubscriptionsItemsIndexMeta": {
                "type": "object",
                "properties": {
                    "page": {
                        "$ref": "#/components/schemas/Page"
                    }
                },
                "additionalProperties": false,
                "required": [
                    "page"
                ]
            },
            "CustomerSubscriptionsShowMeta": {
                "type": "object",
                "properties": {
                    "actions": {
                        "type": "array",
                        "items": {
                            "type": "string",
                            "enum": [
                                "restore"
                            ]
                        },
                        "description": "The actions the caller may currently invoke on this record."
                    }
                },
                "additionalProperties": false
            },
            "CustomerSummary": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Customer name."
                    },
                    "code": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Short customer code, e.g. ACM."
                    }
                },
                "required": [
                    "id",
                    "name",
                    "code"
                ],
                "description": "A customer, as referenced from another resource."
            },
            "CustomersActionsRestoreMeta": {
                "type": "object",
                "properties": {
                    "warnings": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "code": {
                                    "type": "string",
                                    "enum": [
                                        "already_in_target_state"
                                    ],
                                    "x-extensible-enum": true,
                                    "description": "Machine-readable warning code. Switch on this, never on detail."
                                },
                                "detail": {
                                    "type": "string",
                                    "description": "Human-readable explanation."
                                },
                                "pointer": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "description": "RFC 6901 pointer into the request body, where the warning is about a body field."
                                },
                                "parameter": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "description": "The query-parameter name, brackets included, where the warning is about a parameter."
                                },
                                "sunset": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "format": "date",
                                    "description": "Date the deprecated thing stops working."
                                },
                                "replacement": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "description": "What to use instead."
                                }
                            },
                            "required": [
                                "code",
                                "detail"
                            ],
                            "description": "An advisory about this request. It never changes the status code or `data`."
                        },
                        "description": "Non-fatal notes about this request. Switch on `code`, never on `detail`."
                    }
                },
                "additionalProperties": false
            },
            "CustomersIndexMeta": {
                "type": "object",
                "properties": {
                    "page": {
                        "$ref": "#/components/schemas/Page"
                    },
                    "warnings": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "code": {
                                    "type": "string",
                                    "enum": [
                                        "include_truncated"
                                    ],
                                    "x-extensible-enum": true,
                                    "description": "Machine-readable warning code. Switch on this, never on detail."
                                },
                                "detail": {
                                    "type": "string",
                                    "description": "Human-readable explanation."
                                },
                                "pointer": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "description": "RFC 6901 pointer into the request body, where the warning is about a body field."
                                },
                                "parameter": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "description": "The query-parameter name, brackets included, where the warning is about a parameter."
                                },
                                "sunset": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "format": "date",
                                    "description": "Date the deprecated thing stops working."
                                },
                                "replacement": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "description": "What to use instead."
                                }
                            },
                            "required": [
                                "code",
                                "detail"
                            ],
                            "description": "An advisory about this request. It never changes the status code or `data`."
                        },
                        "description": "Non-fatal notes about this request. Switch on `code`, never on `detail`."
                    }
                },
                "additionalProperties": false,
                "required": [
                    "page"
                ]
            },
            "CustomersShowMeta": {
                "type": "object",
                "properties": {
                    "actions": {
                        "type": "array",
                        "items": {
                            "type": "string",
                            "enum": [
                                "restore"
                            ]
                        },
                        "description": "The actions the caller may currently invoke on this record."
                    }
                },
                "additionalProperties": false
            },
            "DiscoveryEntry": {
                "type": "object",
                "properties": {
                    "name": {
                        "type": "string",
                        "description": "Human-readable name of the resource collection."
                    },
                    "path": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Absolute URL of the collection endpoint, or `null` where this resource registers no flat collection and is reached only through a parent or by id."
                    },
                    "item_path": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Templated absolute URL of the single-resource endpoint, with the path parameter left as a `{placeholder}`. Null where the resource has no flat route at all."
                    },
                    "permission_prefix": {
                        "type": "string",
                        "description": "The permission prefix this resource is gated on. It is the value after the class in an `api:{class}:{prefix}` token ability."
                    },
                    "summary_schema": {
                        "type": "string",
                        "description": "JSON pointer to the OpenAPI component describing this resource in the `summary` view, which is what a collection returns by default."
                    },
                    "detail_schema": {
                        "type": "string",
                        "description": "JSON pointer to the OpenAPI component describing the `detail` view, which is what a single-resource read and every write return."
                    },
                    "operations": {
                        "type": "array",
                        "items": {
                            "type": "string"
                        },
                        "description": "The operations this resource registers, as `index`, `store`, `show`, `update`, `destroy` and `actions.{verb}`. A nested operation keeps its full identifier, so `work-items.comments.index` appears verbatim."
                    }
                },
                "required": [
                    "name",
                    "path",
                    "item_path",
                    "permission_prefix",
                    "summary_schema",
                    "detail_schema",
                    "operations"
                ],
                "description": "One resource this API exposes: where it lives, what it is called in the permission system, which schemas describe it and which operations it registers."
            },
            "DiscoveryEntryList": {
                "type": "object",
                "properties": {
                    "name": {
                        "type": "string",
                        "description": "Human-readable name of the resource collection."
                    },
                    "path": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Absolute URL of the collection endpoint, or `null` where this resource registers no flat collection and is reached only through a parent or by id."
                    },
                    "item_path": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Templated absolute URL of the single-resource endpoint, with the path parameter left as a `{placeholder}`. Null where the resource has no flat route at all."
                    },
                    "permission_prefix": {
                        "type": "string",
                        "description": "The permission prefix this resource is gated on. It is the value after the class in an `api:{class}:{prefix}` token ability."
                    },
                    "summary_schema": {
                        "type": "string",
                        "description": "JSON pointer to the OpenAPI component describing this resource in the `summary` view, which is what a collection returns by default."
                    },
                    "detail_schema": {
                        "type": "string",
                        "description": "JSON pointer to the OpenAPI component describing the `detail` view, which is what a single-resource read and every write return."
                    },
                    "operations": {
                        "type": "array",
                        "items": {
                            "type": "string"
                        },
                        "description": "The operations this resource registers, as `index`, `store`, `show`, `update`, `destroy` and `actions.{verb}`. A nested operation keeps its full identifier, so `work-items.comments.index` appears verbatim."
                    }
                },
                "required": [
                    "name",
                    "path",
                    "item_path",
                    "permission_prefix",
                    "summary_schema",
                    "detail_schema",
                    "operations"
                ],
                "description": "One resource this API exposes: where it lives, what it is called in the permission system, which schemas describe it and which operations it registers."
            },
            "DiscoveryMeta": {
                "type": "object",
                "properties": {
                    "page": {
                        "$ref": "#/components/schemas/Page"
                    }
                },
                "additionalProperties": false,
                "required": [
                    "page"
                ]
            },
            "DriveDocument": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Id of the document body, not of the drive item it belongs to. The two are numbered separately."
                    },
                    "drive_item_id": {
                        "type": "integer",
                        "description": "The drive item this body belongs to. Set on create and immutable afterwards; read the item itself from `GET /api/v2/drive-items/{drive_item}`."
                    },
                    "content": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "markdown",
                        "description": "The document body as GitHub-Flavored Markdown: headings, lists, task lists (`- [ ]`), tables, code fences, links and images all survive. Detail-only: pass `?view=detail` to get it on the collection. Markdown cannot express text colours, highlights, alignment or @mentions, so read `?include=content_html` first if the document may contain them: a read of `content` followed by a PATCH of it discards all four permanently."
                    },
                    "content_html": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "html",
                        "description": "The same document as sanitised HTML, preserving the text colours, highlights, alignment and @mentions Markdown cannot carry. Mention spans round-trip unchanged; constructing a new one is out of scope for v2.0.",
                        "x-plnnrly-on-demand": true
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync. Typing in the collaborative editor moves this, not the drive item's own `updated_at`."
                    },
                    "links": {
                        "$ref": "#/components/schemas/DriveDocumentLinks"
                    }
                },
                "required": [
                    "id",
                    "drive_item_id",
                    "content",
                    "created_at",
                    "updated_at",
                    "links"
                ],
                "description": "The body of a drive item of kind `document`: the rich-editor content plnnrly opens when you click a document in the file browser. It is stored separately from the item, so the item's own metadata (title, folder, tags, owner, access) lives on the drive item and is not repeated here. The body is exchanged as GitHub-Flavored Markdown, with a sanitised-HTML projection available on demand for the colours, highlights, alignment and mentions Markdown cannot express. Access is inherited whole from the owning drive item: a caller who may read that item may read its body, and a caller who may edit it may write one."
            },
            "DriveDocumentLinks": {
                "type": "object",
                "properties": {
                    "self": {
                        "type": "string",
                        "format": "uri",
                        "description": "The canonical URI of this record."
                    }
                },
                "required": [
                    "self"
                ],
                "additionalProperties": false
            },
            "DriveDocumentList": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Id of the document body, not of the drive item it belongs to. The two are numbered separately."
                    },
                    "drive_item_id": {
                        "type": "integer",
                        "description": "The drive item this body belongs to. Set on create and immutable afterwards; read the item itself from `GET /api/v2/drive-items/{drive_item}`."
                    },
                    "content_html": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "html",
                        "description": "The same document as sanitised HTML, preserving the text colours, highlights, alignment and @mentions Markdown cannot carry. Mention spans round-trip unchanged; constructing a new one is out of scope for v2.0.",
                        "x-plnnrly-on-demand": true
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync. Typing in the collaborative editor moves this, not the drive item's own `updated_at`."
                    },
                    "links": {
                        "$ref": "#/components/schemas/DriveDocumentLinks"
                    }
                },
                "required": [
                    "id",
                    "drive_item_id",
                    "created_at",
                    "updated_at",
                    "links"
                ],
                "description": "The body of a drive item of kind `document`: the rich-editor content plnnrly opens when you click a document in the file browser. It is stored separately from the item, so the item's own metadata (title, folder, tags, owner, access) lives on the drive item and is not repeated here. The body is exchanged as GitHub-Flavored Markdown, with a sanitised-HTML projection available on demand for the colours, highlights, alignment and mentions Markdown cannot express. Access is inherited whole from the owning drive item: a caller who may read that item may read its body, and a caller who may edit it may write one."
            },
            "DriveDocumentsIndexMeta": {
                "type": "object",
                "properties": {
                    "page": {
                        "$ref": "#/components/schemas/PageOptionalTotal"
                    }
                },
                "additionalProperties": false,
                "required": [
                    "page"
                ]
            },
            "DriveFolder": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Folder name as shown in the drive tree."
                    },
                    "parent_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Folder this one sits in, or `null` at the drive root."
                    },
                    "parent_folder": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/DriveFolderSummary"
                            },
                            {
                                "type": "null"
                            }
                        ],
                        "description": "The folder this one sits in; absent for folders at the drive root."
                    },
                    "owner_type": {
                        "type": "string",
                        "enum": [
                            "project",
                            "customer",
                            "organization",
                            "user"
                        ],
                        "x-extensible-enum": true,
                        "description": "The kind of record `owner` points at; it says which branch of `owner` applies."
                    },
                    "owner_id": {
                        "type": "integer",
                        "description": "The id of the record `owner` points at."
                    },
                    "owner": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/ProjectSummary"
                            },
                            {
                                "$ref": "#/components/schemas/CustomerSummary"
                            },
                            {
                                "$ref": "#/components/schemas/UserSummary"
                            }
                        ],
                        "x-plnnrly-discriminator": {
                            "propertyName": "owner_type",
                            "mapping": {
                                "project": "#/components/schemas/ProjectSummary",
                                "customer": "#/components/schemas/CustomerSummary",
                                "user": "#/components/schemas/UserSummary"
                            }
                        },
                        "x-plnnrly-morph-aliases": [
                            "project",
                            "customer",
                            "organization",
                            "user"
                        ],
                        "description": "The project, customer, organisation or user whose drive this folder lives in. An organisation-owned folder renders as a bare {\"id\": N} reference."
                    },
                    "order": {
                        "type": "integer",
                        "description": "Sort position within the drive and level, as shown in the drive tree. Positions count per drive and level, so the sequence is only meaningful once the collection is narrowed to one drive and one level. It is not assigned on create, so a folder created through the API stays at 0 until it is positioned in plnnrly; reordering is not exposed in v2.0.",
                        "x-plnnrly-server-assigned": true
                    },
                    "created_by": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Author; always the authenticated user at create time. Null on legacy rows and where the author has since been deleted.",
                        "x-plnnrly-server-assigned": true
                    },
                    "inherits_permissions": {
                        "type": "boolean",
                        "description": "Whether this folder takes its permissions from its parent."
                    },
                    "general_access": {
                        "type": "string",
                        "enum": [
                            "everyone_edit",
                            "everyone_view",
                            "restricted"
                        ],
                        "x-extensible-enum": true,
                        "description": "Baseline sharing tier, consulted only while `inherits_permissions` is `false`."
                    },
                    "access": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "enum": [
                            "view",
                            "edit",
                            null
                        ],
                        "x-extensible-enum": true,
                        "description": "The calling user's effective access level on this folder, after the permission for the owner type, the access inherited from folders and any per-folder grant."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "drive_items_count": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Live drive items directly in this folder that the caller may see. Archived items are not counted, and neither are items in subfolders."
                    },
                    "links": {
                        "$ref": "#/components/schemas/DriveFolderLinks"
                    }
                },
                "required": [
                    "id",
                    "name",
                    "parent_id",
                    "parent_folder",
                    "owner_type",
                    "owner_id",
                    "owner",
                    "order",
                    "created_by",
                    "inherits_permissions",
                    "general_access",
                    "access",
                    "created_at",
                    "updated_at",
                    "drive_items_count",
                    "links"
                ],
                "description": "A folder in a drive: the tree plnnrly's file browser draws under a project, a customer, the organisation drive or a user's personal drive. A folder holds drive items and other folders, and it is where the drive's access rules are anchored: an item or a subfolder that inherits permissions takes them from the nearest ancestor folder that does not."
            },
            "DriveFolderLinks": {
                "type": "object",
                "properties": {
                    "self": {
                        "type": "string",
                        "format": "uri",
                        "description": "The canonical URI of this record."
                    },
                    "drive_items": {
                        "type": "string",
                        "format": "uri",
                        "description": "Drive items directly in this folder."
                    }
                },
                "required": [
                    "self",
                    "drive_items"
                ],
                "additionalProperties": false
            },
            "DriveFolderList": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Folder name as shown in the drive tree."
                    },
                    "parent_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Folder this one sits in, or `null` at the drive root."
                    },
                    "parent_folder": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/DriveFolderSummary"
                            },
                            {
                                "type": "null"
                            }
                        ],
                        "description": "The folder this one sits in; absent for folders at the drive root."
                    },
                    "owner_type": {
                        "type": "string",
                        "enum": [
                            "project",
                            "customer",
                            "organization",
                            "user"
                        ],
                        "x-extensible-enum": true,
                        "description": "The kind of record `owner` points at; it says which branch of `owner` applies."
                    },
                    "owner_id": {
                        "type": "integer",
                        "description": "The id of the record `owner` points at."
                    },
                    "owner": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/ProjectSummary"
                            },
                            {
                                "$ref": "#/components/schemas/CustomerSummary"
                            },
                            {
                                "$ref": "#/components/schemas/UserSummary"
                            }
                        ],
                        "x-plnnrly-discriminator": {
                            "propertyName": "owner_type",
                            "mapping": {
                                "project": "#/components/schemas/ProjectSummary",
                                "customer": "#/components/schemas/CustomerSummary",
                                "user": "#/components/schemas/UserSummary"
                            }
                        },
                        "x-plnnrly-morph-aliases": [
                            "project",
                            "customer",
                            "organization",
                            "user"
                        ],
                        "description": "The project, customer, organisation or user whose drive this folder lives in. An organisation-owned folder renders as a bare {\"id\": N} reference."
                    },
                    "order": {
                        "type": "integer",
                        "description": "Sort position within the drive and level, as shown in the drive tree. Positions count per drive and level, so the sequence is only meaningful once the collection is narrowed to one drive and one level. It is not assigned on create, so a folder created through the API stays at 0 until it is positioned in plnnrly; reordering is not exposed in v2.0.",
                        "x-plnnrly-server-assigned": true
                    },
                    "created_by": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Author; always the authenticated user at create time. Null on legacy rows and where the author has since been deleted.",
                        "x-plnnrly-server-assigned": true
                    },
                    "inherits_permissions": {
                        "type": "boolean",
                        "description": "Whether this folder takes its permissions from its parent."
                    },
                    "general_access": {
                        "type": "string",
                        "enum": [
                            "everyone_edit",
                            "everyone_view",
                            "restricted"
                        ],
                        "x-extensible-enum": true,
                        "description": "Baseline sharing tier, consulted only while `inherits_permissions` is `false`."
                    },
                    "access": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "enum": [
                            "view",
                            "edit",
                            null
                        ],
                        "x-extensible-enum": true,
                        "description": "The calling user's effective access level on this folder, after the permission for the owner type, the access inherited from folders and any per-folder grant."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "drive_items_count": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Live drive items directly in this folder that the caller may see. Archived items are not counted, and neither are items in subfolders."
                    },
                    "links": {
                        "$ref": "#/components/schemas/DriveFolderLinks"
                    }
                },
                "required": [
                    "id",
                    "name",
                    "parent_id",
                    "parent_folder",
                    "owner_type",
                    "owner_id",
                    "owner",
                    "order",
                    "created_by",
                    "inherits_permissions",
                    "general_access",
                    "access",
                    "created_at",
                    "updated_at",
                    "drive_items_count",
                    "links"
                ],
                "description": "A folder in a drive: the tree plnnrly's file browser draws under a project, a customer, the organisation drive or a user's personal drive. A folder holds drive items and other folders, and it is where the drive's access rules are anchored: an item or a subfolder that inherits permissions takes them from the nearest ancestor folder that does not."
            },
            "DriveFolderSummary": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Folder name as shown in the drive tree."
                    }
                },
                "required": [
                    "id",
                    "name"
                ],
                "description": "A drive folder, as referenced from another resource."
            },
            "DriveFoldersIndexMeta": {
                "type": "object",
                "properties": {
                    "page": {
                        "$ref": "#/components/schemas/PageOptionalTotal"
                    }
                },
                "additionalProperties": false,
                "required": [
                    "page"
                ]
            },
            "DriveItem": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "title": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Item title."
                    },
                    "group": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Free-text grouping label shown in the drive tree."
                    },
                    "kind": {
                        "type": "string",
                        "enum": [
                            "document",
                            "pdf",
                            "image",
                            "audio",
                            "video",
                            "office",
                            "other"
                        ],
                        "x-extensible-enum": true,
                        "description": "What this item is. Everything created through the API is a `document`; the other kinds come from uploads in plnnrly."
                    },
                    "source": {
                        "type": "string",
                        "enum": [
                            "builtin",
                            "upload"
                        ],
                        "x-extensible-enum": true,
                        "description": "Where the item came from: `builtin` for an editor document, `upload` for a file uploaded in plnnrly."
                    },
                    "folder_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Folder this item sits in, or `null` at the drive root."
                    },
                    "drive_folder": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/DriveFolderSummary"
                            },
                            {
                                "type": "null"
                            }
                        ],
                        "description": "The folder this item sits in; absent for items at the drive root."
                    },
                    "owner_type": {
                        "type": "string",
                        "enum": [
                            "project",
                            "customer",
                            "organization",
                            "user"
                        ],
                        "x-extensible-enum": true,
                        "description": "The kind of record `owner` points at; it says which branch of `owner` applies."
                    },
                    "owner_id": {
                        "type": "integer",
                        "description": "The id of the record `owner` points at."
                    },
                    "owner": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/ProjectSummary"
                            },
                            {
                                "$ref": "#/components/schemas/CustomerSummary"
                            },
                            {
                                "$ref": "#/components/schemas/UserSummary"
                            }
                        ],
                        "x-plnnrly-discriminator": {
                            "propertyName": "owner_type",
                            "mapping": {
                                "project": "#/components/schemas/ProjectSummary",
                                "customer": "#/components/schemas/CustomerSummary",
                                "user": "#/components/schemas/UserSummary"
                            }
                        },
                        "x-plnnrly-morph-aliases": [
                            "project",
                            "customer",
                            "organization",
                            "user"
                        ],
                        "description": "The project, customer, organisation or user whose drive this item lives in. An organisation-owned item renders as a bare {\"id\": N} reference."
                    },
                    "content_record_type": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "enum": [
                            "drive_document",
                            null
                        ],
                        "x-extensible-enum": true,
                        "description": "The kind of record `content_record` points at; it says which branch of `content_record` applies."
                    },
                    "content_record_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "The id of the record `content_record` points at."
                    },
                    "content_record": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "type": "null"
                            }
                        ],
                        "x-plnnrly-discriminator": {
                            "propertyName": "content_record_type",
                            "mapping": []
                        },
                        "x-plnnrly-morph-aliases": [
                            "drive_document"
                        ],
                        "description": "The body this item points at: a drive document for an editor document, `null` for an item with no body yet. Renders as a bare {\"id\": N} reference; read the body from `GET /api/v2/drive-documents/{drive_document}`."
                    },
                    "order_column": {
                        "type": "integer",
                        "description": "Sort position, assigned by the server. Positions count per drive and folder, so the sequence is only meaningful once the collection is narrowed to one drive and one folder. Reordering is not exposed in v2.0.",
                        "x-plnnrly-server-assigned": true
                    },
                    "created_by": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Author; always the authenticated user at create time. Null on legacy rows and where the author has since been deleted.",
                        "x-plnnrly-server-assigned": true
                    },
                    "archived_by": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "The user who archived this item; set on delete and cleared on restore.",
                        "x-plnnrly-server-assigned": true
                    },
                    "inherits_permissions": {
                        "type": "boolean",
                        "description": "Whether this item takes its permissions from its folder."
                    },
                    "general_access": {
                        "type": "string",
                        "enum": [
                            "everyone_edit",
                            "everyone_view",
                            "restricted"
                        ],
                        "x-extensible-enum": true,
                        "description": "Baseline sharing tier, consulted only while `inherits_permissions` is `false`."
                    },
                    "access": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "enum": [
                            "view",
                            "edit",
                            null
                        ],
                        "x-extensible-enum": true,
                        "description": "The calling user's effective access level on this item, after the permission for the owner type, the access inherited from folders and any per-item grant."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "deleted_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "When this item was archived, or `null` while it is live."
                    },
                    "comments_count": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Comments on this item."
                    },
                    "files_count": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Files attached to this item, counting the attachments list in plnnrly, the single uploaded file and the images embedded in the document body."
                    },
                    "tags_count": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Tags on this item."
                    },
                    "tags": {
                        "$ref": "#/components/schemas/TagSummaryInclude",
                        "description": "Tags on this item. An item takes at most 50 tags, so the include lists them all. `tag_ids` replaces the whole set.",
                        "x-plnnrly-include-only": true
                    },
                    "links": {
                        "$ref": "#/components/schemas/DriveItemLinks"
                    }
                },
                "required": [
                    "id",
                    "title",
                    "group",
                    "kind",
                    "source",
                    "folder_id",
                    "drive_folder",
                    "owner_type",
                    "owner_id",
                    "owner",
                    "content_record_type",
                    "content_record_id",
                    "content_record",
                    "order_column",
                    "created_by",
                    "archived_by",
                    "inherits_permissions",
                    "general_access",
                    "access",
                    "created_at",
                    "updated_at",
                    "deleted_at",
                    "comments_count",
                    "files_count",
                    "tags_count",
                    "links"
                ],
                "description": "A file or document in a drive: the rows plnnrly's file browser lists under a project, a customer, the organisation drive or a user's personal drive. A drive item is the envelope (title, folder, tags, sharing tier), and its body, where it has one, lives on the drive document it points at through `content_record`. Access is decided per item: the permission for the owner type, then the folders it inherits from, then any per-item grant."
            },
            "DriveItemLinks": {
                "type": "object",
                "properties": {
                    "self": {
                        "type": "string",
                        "format": "uri",
                        "description": "The canonical URI of this record."
                    },
                    "comments": {
                        "type": "string",
                        "format": "uri",
                        "description": "Comments on this drive item."
                    },
                    "files": {
                        "type": "string",
                        "format": "uri",
                        "description": "Files stored on this drive item."
                    }
                },
                "required": [
                    "self",
                    "comments",
                    "files"
                ],
                "additionalProperties": false
            },
            "DriveItemList": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "title": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Item title."
                    },
                    "group": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Free-text grouping label shown in the drive tree."
                    },
                    "kind": {
                        "type": "string",
                        "enum": [
                            "document",
                            "pdf",
                            "image",
                            "audio",
                            "video",
                            "office",
                            "other"
                        ],
                        "x-extensible-enum": true,
                        "description": "What this item is. Everything created through the API is a `document`; the other kinds come from uploads in plnnrly."
                    },
                    "source": {
                        "type": "string",
                        "enum": [
                            "builtin",
                            "upload"
                        ],
                        "x-extensible-enum": true,
                        "description": "Where the item came from: `builtin` for an editor document, `upload` for a file uploaded in plnnrly."
                    },
                    "folder_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Folder this item sits in, or `null` at the drive root."
                    },
                    "drive_folder": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/DriveFolderSummary"
                            },
                            {
                                "type": "null"
                            }
                        ],
                        "description": "The folder this item sits in; absent for items at the drive root."
                    },
                    "owner_type": {
                        "type": "string",
                        "enum": [
                            "project",
                            "customer",
                            "organization",
                            "user"
                        ],
                        "x-extensible-enum": true,
                        "description": "The kind of record `owner` points at; it says which branch of `owner` applies."
                    },
                    "owner_id": {
                        "type": "integer",
                        "description": "The id of the record `owner` points at."
                    },
                    "owner": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/ProjectSummary"
                            },
                            {
                                "$ref": "#/components/schemas/CustomerSummary"
                            },
                            {
                                "$ref": "#/components/schemas/UserSummary"
                            }
                        ],
                        "x-plnnrly-discriminator": {
                            "propertyName": "owner_type",
                            "mapping": {
                                "project": "#/components/schemas/ProjectSummary",
                                "customer": "#/components/schemas/CustomerSummary",
                                "user": "#/components/schemas/UserSummary"
                            }
                        },
                        "x-plnnrly-morph-aliases": [
                            "project",
                            "customer",
                            "organization",
                            "user"
                        ],
                        "description": "The project, customer, organisation or user whose drive this item lives in. An organisation-owned item renders as a bare {\"id\": N} reference."
                    },
                    "content_record_type": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "enum": [
                            "drive_document",
                            null
                        ],
                        "x-extensible-enum": true,
                        "description": "The kind of record `content_record` points at; it says which branch of `content_record` applies."
                    },
                    "content_record_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "The id of the record `content_record` points at."
                    },
                    "content_record": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "type": "null"
                            }
                        ],
                        "x-plnnrly-discriminator": {
                            "propertyName": "content_record_type",
                            "mapping": []
                        },
                        "x-plnnrly-morph-aliases": [
                            "drive_document"
                        ],
                        "description": "The body this item points at: a drive document for an editor document, `null` for an item with no body yet. Renders as a bare {\"id\": N} reference; read the body from `GET /api/v2/drive-documents/{drive_document}`."
                    },
                    "order_column": {
                        "type": "integer",
                        "description": "Sort position, assigned by the server. Positions count per drive and folder, so the sequence is only meaningful once the collection is narrowed to one drive and one folder. Reordering is not exposed in v2.0.",
                        "x-plnnrly-server-assigned": true
                    },
                    "created_by": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Author; always the authenticated user at create time. Null on legacy rows and where the author has since been deleted.",
                        "x-plnnrly-server-assigned": true
                    },
                    "archived_by": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "The user who archived this item; set on delete and cleared on restore.",
                        "x-plnnrly-server-assigned": true
                    },
                    "inherits_permissions": {
                        "type": "boolean",
                        "description": "Whether this item takes its permissions from its folder."
                    },
                    "general_access": {
                        "type": "string",
                        "enum": [
                            "everyone_edit",
                            "everyone_view",
                            "restricted"
                        ],
                        "x-extensible-enum": true,
                        "description": "Baseline sharing tier, consulted only while `inherits_permissions` is `false`."
                    },
                    "access": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "enum": [
                            "view",
                            "edit",
                            null
                        ],
                        "x-extensible-enum": true,
                        "description": "The calling user's effective access level on this item, after the permission for the owner type, the access inherited from folders and any per-item grant."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "deleted_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "When this item was archived, or `null` while it is live."
                    },
                    "comments_count": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Comments on this item."
                    },
                    "files_count": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Files attached to this item, counting the attachments list in plnnrly, the single uploaded file and the images embedded in the document body."
                    },
                    "tags_count": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Tags on this item."
                    },
                    "tags": {
                        "$ref": "#/components/schemas/TagSummaryInclude",
                        "description": "Tags on this item. An item takes at most 50 tags, so the include lists them all. `tag_ids` replaces the whole set.",
                        "x-plnnrly-include-only": true
                    },
                    "links": {
                        "$ref": "#/components/schemas/DriveItemLinks"
                    }
                },
                "required": [
                    "id",
                    "title",
                    "group",
                    "kind",
                    "source",
                    "folder_id",
                    "drive_folder",
                    "owner_type",
                    "owner_id",
                    "owner",
                    "content_record_type",
                    "content_record_id",
                    "content_record",
                    "order_column",
                    "created_by",
                    "archived_by",
                    "inherits_permissions",
                    "general_access",
                    "access",
                    "created_at",
                    "updated_at",
                    "deleted_at",
                    "comments_count",
                    "files_count",
                    "tags_count",
                    "links"
                ],
                "description": "A file or document in a drive: the rows plnnrly's file browser lists under a project, a customer, the organisation drive or a user's personal drive. A drive item is the envelope (title, folder, tags, sharing tier), and its body, where it has one, lives on the drive document it points at through `content_record`. Access is decided per item: the permission for the owner type, then the folders it inherits from, then any per-item grant."
            },
            "DriveItemSummary": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "title": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Item title."
                    },
                    "owner_type": {
                        "type": "string",
                        "enum": [
                            "project",
                            "customer",
                            "organization",
                            "user"
                        ],
                        "x-extensible-enum": true,
                        "description": "The kind of record `owner` points at; it says which branch of `owner` applies."
                    },
                    "owner_id": {
                        "type": "integer",
                        "description": "The id of the record `owner` points at."
                    },
                    "owner": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            }
                        ],
                        "x-plnnrly-discriminator": {
                            "propertyName": "owner_type",
                            "mapping": []
                        },
                        "x-plnnrly-morph-aliases": [
                            "project",
                            "customer",
                            "organization",
                            "user"
                        ],
                        "description": "The project, customer, organisation or user whose drive this item lives in."
                    }
                },
                "required": [
                    "id",
                    "title",
                    "owner_type",
                    "owner_id",
                    "owner"
                ],
                "description": "A drive item, as referenced from another resource."
            },
            "DriveItemSummaryInclude": {
                "type": "object",
                "properties": {
                    "data": {
                        "type": "array",
                        "items": {
                            "$ref": "#/components/schemas/DriveItemSummary"
                        },
                        "maxItems": 25
                    },
                    "meta": {
                        "type": "object",
                        "properties": {
                            "count": {
                                "type": "integer",
                                "minimum": 0
                            },
                            "truncated": {
                                "type": "boolean"
                            }
                        },
                        "required": [
                            "truncated"
                        ],
                        "additionalProperties": false
                    }
                },
                "required": [
                    "data",
                    "meta"
                ],
                "additionalProperties": false
            },
            "DriveItemsActionsRestoreMeta": {
                "type": "object",
                "properties": {
                    "warnings": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "code": {
                                    "type": "string",
                                    "enum": [
                                        "already_in_target_state"
                                    ],
                                    "x-extensible-enum": true,
                                    "description": "Machine-readable warning code. Switch on this, never on detail."
                                },
                                "detail": {
                                    "type": "string",
                                    "description": "Human-readable explanation."
                                },
                                "pointer": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "description": "RFC 6901 pointer into the request body, where the warning is about a body field."
                                },
                                "parameter": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "description": "The query-parameter name, brackets included, where the warning is about a parameter."
                                },
                                "sunset": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "format": "date",
                                    "description": "Date the deprecated thing stops working."
                                },
                                "replacement": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "description": "What to use instead."
                                }
                            },
                            "required": [
                                "code",
                                "detail"
                            ],
                            "description": "An advisory about this request. It never changes the status code or `data`."
                        },
                        "description": "Non-fatal notes about this request. Switch on `code`, never on `detail`."
                    }
                },
                "additionalProperties": false
            },
            "DriveItemsCommentsIndexMeta": {
                "type": "object",
                "properties": {
                    "page": {
                        "$ref": "#/components/schemas/PageOptionalTotal"
                    }
                },
                "additionalProperties": false,
                "required": [
                    "page"
                ]
            },
            "DriveItemsFilesIndexMeta": {
                "type": "object",
                "properties": {
                    "page": {
                        "$ref": "#/components/schemas/Page"
                    },
                    "warnings": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "code": {
                                    "type": "string",
                                    "enum": [
                                        "signed_url_unavailable"
                                    ],
                                    "x-extensible-enum": true,
                                    "description": "Machine-readable warning code. Switch on this, never on detail."
                                },
                                "detail": {
                                    "type": "string",
                                    "description": "Human-readable explanation."
                                },
                                "pointer": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "description": "RFC 6901 pointer into the request body, where the warning is about a body field."
                                },
                                "parameter": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "description": "The query-parameter name, brackets included, where the warning is about a parameter."
                                },
                                "sunset": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "format": "date",
                                    "description": "Date the deprecated thing stops working."
                                },
                                "replacement": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "description": "What to use instead."
                                }
                            },
                            "required": [
                                "code",
                                "detail"
                            ],
                            "description": "An advisory about this request. It never changes the status code or `data`."
                        },
                        "description": "Non-fatal notes about this request. Switch on `code`, never on `detail`."
                    }
                },
                "additionalProperties": false,
                "required": [
                    "page"
                ]
            },
            "DriveItemsFilesStoreMeta": {
                "type": "object",
                "properties": {
                    "warnings": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "code": {
                                    "type": "string",
                                    "enum": [
                                        "signed_url_unavailable"
                                    ],
                                    "x-extensible-enum": true,
                                    "description": "Machine-readable warning code. Switch on this, never on detail."
                                },
                                "detail": {
                                    "type": "string",
                                    "description": "Human-readable explanation."
                                },
                                "pointer": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "description": "RFC 6901 pointer into the request body, where the warning is about a body field."
                                },
                                "parameter": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "description": "The query-parameter name, brackets included, where the warning is about a parameter."
                                },
                                "sunset": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "format": "date",
                                    "description": "Date the deprecated thing stops working."
                                },
                                "replacement": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "description": "What to use instead."
                                }
                            },
                            "required": [
                                "code",
                                "detail"
                            ],
                            "description": "An advisory about this request. It never changes the status code or `data`."
                        },
                        "description": "Non-fatal notes about this request. Switch on `code`, never on `detail`."
                    }
                },
                "additionalProperties": false
            },
            "DriveItemsIndexMeta": {
                "type": "object",
                "properties": {
                    "page": {
                        "$ref": "#/components/schemas/PageOptionalTotal"
                    },
                    "warnings": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "code": {
                                    "type": "string",
                                    "enum": [
                                        "include_truncated"
                                    ],
                                    "x-extensible-enum": true,
                                    "description": "Machine-readable warning code. Switch on this, never on detail."
                                },
                                "detail": {
                                    "type": "string",
                                    "description": "Human-readable explanation."
                                },
                                "pointer": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "description": "RFC 6901 pointer into the request body, where the warning is about a body field."
                                },
                                "parameter": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "description": "The query-parameter name, brackets included, where the warning is about a parameter."
                                },
                                "sunset": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "format": "date",
                                    "description": "Date the deprecated thing stops working."
                                },
                                "replacement": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "description": "What to use instead."
                                }
                            },
                            "required": [
                                "code",
                                "detail"
                            ],
                            "description": "An advisory about this request. It never changes the status code or `data`."
                        },
                        "description": "Non-fatal notes about this request. Switch on `code`, never on `detail`."
                    }
                },
                "additionalProperties": false,
                "required": [
                    "page"
                ]
            },
            "DriveItemsShowMeta": {
                "type": "object",
                "properties": {
                    "actions": {
                        "type": "array",
                        "items": {
                            "type": "string",
                            "enum": [
                                "restore"
                            ]
                        },
                        "description": "The actions the caller may currently invoke on this record."
                    }
                },
                "additionalProperties": false
            },
            "DuplicateResourceProblem": {
                "type": "object",
                "properties": {
                    "type": {
                        "type": "string",
                        "format": "uri",
                        "const": "https://developers.plnnrly.com/api/v2/problems/duplicate-resource",
                        "description": "The problem type. This is the only member a client switches on."
                    },
                    "title": {
                        "type": "string",
                        "description": "Short, stable per type, English, never interpolated. Never parse it."
                    },
                    "status": {
                        "type": "integer",
                        "const": 409,
                        "description": "The HTTP status, repeated in the body."
                    },
                    "detail": {
                        "type": "string",
                        "description": "Human-readable explanation. May quote values from your request; never shows internal error output."
                    },
                    "instance": {
                        "type": "string",
                        "description": "The request path, with no query string."
                    },
                    "request_id": {
                        "type": "string",
                        "description": "The id also in the X-Request-Id header. Quote it when you report a problem to plnnrly support."
                    },
                    "conflict": {
                        "type": "object",
                        "properties": {
                            "field": {
                                "type": "string",
                                "description": "The field whose value is already taken."
                            },
                            "value": {
                                "type": "string",
                                "description": "The rejected value."
                            }
                        },
                        "required": [
                            "field",
                            "value"
                        ],
                        "description": "Machine-readable context for the uniqueness failure."
                    }
                },
                "required": [
                    "type",
                    "title",
                    "status",
                    "detail",
                    "instance",
                    "request_id"
                ],
                "description": "Duplicate resource"
            },
            "EndpointNotFoundProblem": {
                "type": "object",
                "properties": {
                    "type": {
                        "type": "string",
                        "format": "uri",
                        "const": "https://developers.plnnrly.com/api/v2/problems/endpoint-not-found",
                        "description": "The problem type. This is the only member a client switches on."
                    },
                    "title": {
                        "type": "string",
                        "description": "Short, stable per type, English, never interpolated. Never parse it."
                    },
                    "status": {
                        "type": "integer",
                        "const": 404,
                        "description": "The HTTP status, repeated in the body."
                    },
                    "detail": {
                        "type": "string",
                        "description": "Human-readable explanation. May quote values from your request; never shows internal error output."
                    },
                    "instance": {
                        "type": "string",
                        "description": "The request path, with no query string."
                    },
                    "request_id": {
                        "type": "string",
                        "description": "The id also in the X-Request-Id header. Quote it when you report a problem to plnnrly support."
                    }
                },
                "required": [
                    "type",
                    "title",
                    "status",
                    "detail",
                    "instance",
                    "request_id"
                ],
                "description": "Endpoint not found"
            },
            "EndpointRetiredProblem": {
                "type": "object",
                "properties": {
                    "type": {
                        "type": "string",
                        "format": "uri",
                        "const": "https://developers.plnnrly.com/api/v2/problems/endpoint-retired",
                        "description": "The problem type. This is the only member a client switches on."
                    },
                    "title": {
                        "type": "string",
                        "description": "Short, stable per type, English, never interpolated. Never parse it."
                    },
                    "status": {
                        "type": "integer",
                        "const": 410,
                        "description": "The HTTP status, repeated in the body."
                    },
                    "detail": {
                        "type": "string",
                        "description": "Human-readable explanation. May quote values from your request; never shows internal error output."
                    },
                    "instance": {
                        "type": "string",
                        "description": "The request path, with no query string."
                    },
                    "request_id": {
                        "type": "string",
                        "description": "The id also in the X-Request-Id header. Quote it when you report a problem to plnnrly support."
                    },
                    "sunset": {
                        "type": "string",
                        "format": "date",
                        "description": "The date this endpoint stopped working."
                    },
                    "replaced_by": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "The route name that replaces it."
                    }
                },
                "required": [
                    "type",
                    "title",
                    "status",
                    "detail",
                    "instance",
                    "request_id"
                ],
                "description": "Endpoint retired"
            },
            "Event": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "user_id": {
                        "type": "integer",
                        "description": "Whose agenda the event is on. Always the authenticated user at creation time; not writable.",
                        "x-plnnrly-server-assigned": true
                    },
                    "task_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Kanban task this event is planned against, or `null` for a plain calendar block. Only an event with a task can have time logged on it."
                    },
                    "parent_event_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Master event, when this row is a separated occurrence of a recurring series. Set by the server; not writable.",
                        "x-plnnrly-server-assigned": true
                    },
                    "occurrence_date": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date",
                        "description": "The date of the series occurrence this row replaces. Set by the server; not writable.",
                        "x-plnnrly-server-assigned": true
                    },
                    "start": {
                        "type": "string",
                        "format": "date-time",
                        "description": "When the event starts. RFC 3339 UTC."
                    },
                    "end": {
                        "type": "string",
                        "format": "date-time",
                        "description": "When the event ends. RFC 3339 UTC."
                    },
                    "title": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Event title, as shown on the calendar tile."
                    },
                    "description": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "html",
                        "description": "Free-text description, authored in the rich editor. HTML, sanitised on the way in and on the way out; `@`-mention spans are preserved, and changing them is what notifies the people mentioned."
                    },
                    "is_all_day": {
                        "type": "boolean",
                        "description": "Whether the event occupies whole days rather than a time range. An all-day event is measured against the owner's working week rather than against start and end."
                    },
                    "is_recurring": {
                        "type": "boolean",
                        "description": "Whether this row is the master of a recurring series. When `true`, the recurrence fields describe how occurrences are generated."
                    },
                    "recurrence_interval": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "How many frequency units pass between occurrences: 2 with a weekly frequency is every other week."
                    },
                    "recurrence_frequency": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "enum": [
                            "day",
                            "week",
                            "month",
                            "year",
                            null
                        ],
                        "x-extensible-enum": true,
                        "description": "The unit the series repeats in."
                    },
                    "recurrence_pattern": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "enum": [
                            "day_of_month",
                            "weekday_occurrence",
                            null
                        ],
                        "x-extensible-enum": true,
                        "description": "How a monthly or yearly series positions its occurrence within the month. Ignored for a daily or weekly frequency."
                    },
                    "recurrence_start": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "First moment the series may generate an occurrence."
                    },
                    "recurrence_end": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "Last moment the series may generate an occurrence; `null` for an open-ended series."
                    },
                    "selected_weekdays": {
                        "type": [
                            "array",
                            "null"
                        ],
                        "items": {
                            "type": "string",
                            "enum": [
                                "monday",
                                "tuesday",
                                "wednesday",
                                "thursday",
                                "friday",
                                "saturday",
                                "sunday"
                            ],
                            "x-extensible-enum": true
                        },
                        "description": "Weekdays a recurring event falls on. Applies to a weekly frequency; ignored otherwise."
                    },
                    "recurrence_exceptions": {
                        "type": [
                            "array",
                            "null"
                        ],
                        "items": {
                            "type": "string",
                            "format": "date"
                        },
                        "description": "Dates on which this recurrence does not occur. plnnrly adds one whenever a single occurrence is separated or deleted."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "deleted_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "When the event was deleted, or `null` while it is live. Reach a deleted event with `trashed=with` and undo it with POST `events/{event}/actions/restore`."
                    },
                    "user": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/UserSummary"
                            }
                        ],
                        "description": "The user whose agenda the event is on. Renders as an `{\"id\": N}` reference unless expanded."
                    },
                    "links": {
                        "$ref": "#/components/schemas/EventLinks"
                    }
                },
                "required": [
                    "id",
                    "user_id",
                    "task_id",
                    "parent_event_id",
                    "occurrence_date",
                    "start",
                    "end",
                    "title",
                    "description",
                    "is_all_day",
                    "is_recurring",
                    "recurrence_interval",
                    "recurrence_frequency",
                    "recurrence_pattern",
                    "recurrence_start",
                    "recurrence_end",
                    "selected_weekdays",
                    "recurrence_exceptions",
                    "created_at",
                    "updated_at",
                    "deleted_at",
                    "user",
                    "links"
                ],
                "description": "A planning event: one block on a user's agenda, optionally linked to a kanban task so time can be logged against it. An event is either a one-off or the master of a recurring series, in which case the recurrence fields describe how its occurrences are generated. The occurrences are rendered from those fields and are not rows of their own, except where one was separated in plnnrly.",
                "x-plnnrly-omitted": {
                    "client_id": "The id plnnrly's agenda gives a dropped tile so a repeated create returns the stored event instead of a second one. Only the agenda uses it; an API client neither reads nor sends it."
                }
            },
            "EventLinks": {
                "type": "object",
                "properties": {
                    "self": {
                        "type": "string",
                        "format": "uri",
                        "description": "The canonical URI of this record."
                    }
                },
                "required": [
                    "self"
                ],
                "additionalProperties": false
            },
            "EventList": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "user_id": {
                        "type": "integer",
                        "description": "Whose agenda the event is on. Always the authenticated user at creation time; not writable.",
                        "x-plnnrly-server-assigned": true
                    },
                    "task_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Kanban task this event is planned against, or `null` for a plain calendar block. Only an event with a task can have time logged on it."
                    },
                    "parent_event_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Master event, when this row is a separated occurrence of a recurring series. Set by the server; not writable.",
                        "x-plnnrly-server-assigned": true
                    },
                    "occurrence_date": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date",
                        "description": "The date of the series occurrence this row replaces. Set by the server; not writable.",
                        "x-plnnrly-server-assigned": true
                    },
                    "start": {
                        "type": "string",
                        "format": "date-time",
                        "description": "When the event starts. RFC 3339 UTC."
                    },
                    "end": {
                        "type": "string",
                        "format": "date-time",
                        "description": "When the event ends. RFC 3339 UTC."
                    },
                    "title": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Event title, as shown on the calendar tile."
                    },
                    "is_all_day": {
                        "type": "boolean",
                        "description": "Whether the event occupies whole days rather than a time range. An all-day event is measured against the owner's working week rather than against start and end."
                    },
                    "is_recurring": {
                        "type": "boolean",
                        "description": "Whether this row is the master of a recurring series. When `true`, the recurrence fields describe how occurrences are generated."
                    },
                    "recurrence_interval": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "How many frequency units pass between occurrences: 2 with a weekly frequency is every other week."
                    },
                    "recurrence_frequency": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "enum": [
                            "day",
                            "week",
                            "month",
                            "year",
                            null
                        ],
                        "x-extensible-enum": true,
                        "description": "The unit the series repeats in."
                    },
                    "recurrence_pattern": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "enum": [
                            "day_of_month",
                            "weekday_occurrence",
                            null
                        ],
                        "x-extensible-enum": true,
                        "description": "How a monthly or yearly series positions its occurrence within the month. Ignored for a daily or weekly frequency."
                    },
                    "recurrence_start": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "First moment the series may generate an occurrence."
                    },
                    "recurrence_end": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "Last moment the series may generate an occurrence; `null` for an open-ended series."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "deleted_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "When the event was deleted, or `null` while it is live. Reach a deleted event with `trashed=with` and undo it with POST `events/{event}/actions/restore`."
                    },
                    "user": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/UserSummary"
                            }
                        ],
                        "description": "The user whose agenda the event is on. Renders as an `{\"id\": N}` reference unless expanded."
                    },
                    "links": {
                        "$ref": "#/components/schemas/EventLinks"
                    }
                },
                "required": [
                    "id",
                    "user_id",
                    "task_id",
                    "parent_event_id",
                    "occurrence_date",
                    "start",
                    "end",
                    "title",
                    "is_all_day",
                    "is_recurring",
                    "recurrence_interval",
                    "recurrence_frequency",
                    "recurrence_pattern",
                    "recurrence_start",
                    "recurrence_end",
                    "created_at",
                    "updated_at",
                    "deleted_at",
                    "user",
                    "links"
                ],
                "description": "A planning event: one block on a user's agenda, optionally linked to a kanban task so time can be logged against it. An event is either a one-off or the master of a recurring series, in which case the recurrence fields describe how its occurrences are generated. The occurrences are rendered from those fields and are not rows of their own, except where one was separated in plnnrly."
            },
            "EventSummary": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "title": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Event title."
                    },
                    "start": {
                        "type": "string",
                        "format": "date-time",
                        "description": "When the event starts."
                    }
                },
                "required": [
                    "id",
                    "title",
                    "start"
                ],
                "description": "A planning event, as referenced from another resource."
            },
            "EventsActionsRestoreMeta": {
                "type": "object",
                "properties": {
                    "warnings": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "code": {
                                    "type": "string",
                                    "enum": [
                                        "already_in_target_state"
                                    ],
                                    "x-extensible-enum": true,
                                    "description": "Machine-readable warning code. Switch on this, never on detail."
                                },
                                "detail": {
                                    "type": "string",
                                    "description": "Human-readable explanation."
                                },
                                "pointer": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "description": "RFC 6901 pointer into the request body, where the warning is about a body field."
                                },
                                "parameter": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "description": "The query-parameter name, brackets included, where the warning is about a parameter."
                                },
                                "sunset": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "format": "date",
                                    "description": "Date the deprecated thing stops working."
                                },
                                "replacement": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "description": "What to use instead."
                                }
                            },
                            "required": [
                                "code",
                                "detail"
                            ],
                            "description": "An advisory about this request. It never changes the status code or `data`."
                        },
                        "description": "Non-fatal notes about this request. Switch on `code`, never on `detail`."
                    }
                },
                "additionalProperties": false
            },
            "EventsIndexMeta": {
                "type": "object",
                "properties": {
                    "page": {
                        "$ref": "#/components/schemas/PageOptionalTotal"
                    }
                },
                "additionalProperties": false,
                "required": [
                    "page"
                ]
            },
            "EventsShowMeta": {
                "type": "object",
                "properties": {
                    "actions": {
                        "type": "array",
                        "items": {
                            "type": "string",
                            "enum": [
                                "restore"
                            ]
                        },
                        "description": "The actions the caller may currently invoke on this record."
                    }
                },
                "additionalProperties": false
            },
            "ForbiddenByPolicyProblem": {
                "type": "object",
                "properties": {
                    "type": {
                        "type": "string",
                        "format": "uri",
                        "const": "https://developers.plnnrly.com/api/v2/problems/forbidden-by-policy",
                        "description": "The problem type. This is the only member a client switches on."
                    },
                    "title": {
                        "type": "string",
                        "description": "Short, stable per type, English, never interpolated. Never parse it."
                    },
                    "status": {
                        "type": "integer",
                        "const": 403,
                        "description": "The HTTP status, repeated in the body."
                    },
                    "detail": {
                        "type": "string",
                        "description": "Human-readable explanation. May quote values from your request; never shows internal error output."
                    },
                    "instance": {
                        "type": "string",
                        "description": "The request path, with no query string."
                    },
                    "request_id": {
                        "type": "string",
                        "description": "The id also in the X-Request-Id header. Quote it when you report a problem to plnnrly support."
                    }
                },
                "required": [
                    "type",
                    "title",
                    "status",
                    "detail",
                    "instance",
                    "request_id"
                ],
                "description": "Forbidden by policy"
            },
            "FreeDay": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "description": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Label for the free day, e.g. `Christmas Day`. For a holiday-derived day this is a copy of the holiday name taken when the free day was created, so renaming the holiday does not rewrite it."
                    },
                    "date": {
                        "type": "string",
                        "format": "date",
                        "description": "Calendar date the organisation is closed."
                    },
                    "holiday_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Recurring holiday this free day instantiates, or `null` for a day added by hand. The foreign key nulls on delete, so removing the holiday leaves the free day behind as a manual day still carrying the copied label."
                    },
                    "holiday": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/HolidaySummary"
                            },
                            {
                                "type": "null"
                            }
                        ],
                        "description": "The recurring holiday this free day instantiates; absent for a manually added day."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "links": {
                        "$ref": "#/components/schemas/FreeDayLinks"
                    }
                },
                "required": [
                    "id",
                    "description",
                    "date",
                    "holiday_id",
                    "holiday",
                    "created_at",
                    "updated_at",
                    "links"
                ],
                "description": "A free day: one calendar date on which the organisation is closed, either instantiated from a recurring holiday or added by hand. Free days are excluded from the working days a furlough request consumes. The label is a copy taken at creation time and drifts independently of the holiday it came from."
            },
            "FreeDayLinks": {
                "type": "object",
                "properties": {
                    "self": {
                        "type": "string",
                        "format": "uri",
                        "description": "The canonical URI of this record."
                    }
                },
                "required": [
                    "self"
                ],
                "additionalProperties": false
            },
            "FreeDayList": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "description": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Label for the free day, e.g. `Christmas Day`. For a holiday-derived day this is a copy of the holiday name taken when the free day was created, so renaming the holiday does not rewrite it."
                    },
                    "date": {
                        "type": "string",
                        "format": "date",
                        "description": "Calendar date the organisation is closed."
                    },
                    "holiday_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Recurring holiday this free day instantiates, or `null` for a day added by hand. The foreign key nulls on delete, so removing the holiday leaves the free day behind as a manual day still carrying the copied label."
                    },
                    "holiday": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/HolidaySummary"
                            },
                            {
                                "type": "null"
                            }
                        ],
                        "description": "The recurring holiday this free day instantiates; absent for a manually added day."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "links": {
                        "$ref": "#/components/schemas/FreeDayLinks"
                    }
                },
                "required": [
                    "id",
                    "description",
                    "date",
                    "holiday_id",
                    "holiday",
                    "created_at",
                    "updated_at",
                    "links"
                ],
                "description": "A free day: one calendar date on which the organisation is closed, either instantiated from a recurring holiday or added by hand. Free days are excluded from the working days a furlough request consumes. The label is a copy taken at creation time and drifts independently of the holiday it came from."
            },
            "FreeDaySummary": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "date": {
                        "type": "string",
                        "format": "date",
                        "description": "Calendar date the organisation is closed."
                    },
                    "description": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Label for the free day."
                    }
                },
                "required": [
                    "id",
                    "date",
                    "description"
                ],
                "description": "A free day, as referenced from another resource."
            },
            "FreeDaysIndexMeta": {
                "type": "object",
                "properties": {
                    "page": {
                        "$ref": "#/components/schemas/Page"
                    }
                },
                "additionalProperties": false,
                "required": [
                    "page"
                ]
            },
            "FurloughBalanceMutation": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "user_id": {
                        "type": "integer",
                        "description": "The employee this mutation belongs to. Always the authenticated user on create and not writable.",
                        "x-plnnrly-server-assigned": true
                    },
                    "acceptor_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "The user who approved this mutation, set by the server when the mutation resolves to approved. Null while it is pending, and left `null` by a denial. Resolve the name through `GET /api/v2/users/{user}`.",
                        "x-plnnrly-server-assigned": true
                    },
                    "year": {
                        "type": "integer",
                        "description": "The balance year this mutation counts against. It is not derived from `start`: every balance sum counts by this year, so a mutation whose period moves into another year keeps counting against the year sent here until `year` is sent too."
                    },
                    "type": {
                        "type": "string",
                        "enum": [
                            "regular",
                            "special"
                        ],
                        "x-extensible-enum": true,
                        "description": "Which leave balance this mutation moves. Regular and special leave are separate balances and never offset one another."
                    },
                    "minutes": {
                        "type": "integer",
                        "description": "The signed balance delta in minutes. Negative consumes balance (a leave request); positive grants it (a manager's adjustment, which requires the `furlough::manage` permission to write). The API never computes this from the employee's working week or the organisation's free days: send the exact number of minutes you mean."
                    },
                    "start": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Start of the period this mutation covers, in UTC. Equal to `end` on a manual balance adjustment, which covers no period at all."
                    },
                    "end": {
                        "type": "string",
                        "format": "date-time",
                        "description": "End of the period this mutation covers, in UTC. Never before `start`, and equal to it on a manual balance adjustment."
                    },
                    "description": {
                        "type": "string",
                        "x-plnnrly-multiline": true,
                        "description": "The employee's or manager's own words for this mutation: the reason for the leave, or what the adjustment is for. The only field `?q=` searches."
                    },
                    "is_approved": {
                        "type": [
                            "boolean",
                            "null"
                        ],
                        "description": "Approval state, in three values: `null` means pending approval, `true` approved, `false` denied. The balance counts pending and approved mutations alike and ignores denied ones. Writing this field at all requires the `furlough::manage` permission."
                    },
                    "user": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/UserSummary"
                            }
                        ],
                        "description": "The employee this mutation belongs to. Expanding it with `?include=user` additionally requires the `users::read` permission and a token scope covering the users prefix."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "links": {
                        "$ref": "#/components/schemas/FurloughBalanceMutationLinks"
                    }
                },
                "required": [
                    "id",
                    "user_id",
                    "acceptor_id",
                    "year",
                    "type",
                    "minutes",
                    "start",
                    "end",
                    "description",
                    "is_approved",
                    "user",
                    "created_at",
                    "updated_at",
                    "links"
                ],
                "description": "A furlough balance mutation: one signed ledger entry against an employee's leave balance for a `year` and a leave `type`. A negative `minutes` consumes balance and is a leave request over the period from `start` to `end`; a positive `minutes` grants balance and is an adjustment a manager posts. `is_approved` is three-state and the `null` is semantic: `null` means pending approval, `true` approved, `false` denied. The balance counts the pending and the approved alike, never the denied, so a pending request already reduces what is left."
            },
            "FurloughBalanceMutationLinks": {
                "type": "object",
                "properties": {
                    "self": {
                        "type": "string",
                        "format": "uri",
                        "description": "The canonical URI of this record."
                    }
                },
                "required": [
                    "self"
                ],
                "additionalProperties": false
            },
            "FurloughBalanceMutationList": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "user_id": {
                        "type": "integer",
                        "description": "The employee this mutation belongs to. Always the authenticated user on create and not writable.",
                        "x-plnnrly-server-assigned": true
                    },
                    "acceptor_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "The user who approved this mutation, set by the server when the mutation resolves to approved. Null while it is pending, and left `null` by a denial. Resolve the name through `GET /api/v2/users/{user}`.",
                        "x-plnnrly-server-assigned": true
                    },
                    "year": {
                        "type": "integer",
                        "description": "The balance year this mutation counts against. It is not derived from `start`: every balance sum counts by this year, so a mutation whose period moves into another year keeps counting against the year sent here until `year` is sent too."
                    },
                    "type": {
                        "type": "string",
                        "enum": [
                            "regular",
                            "special"
                        ],
                        "x-extensible-enum": true,
                        "description": "Which leave balance this mutation moves. Regular and special leave are separate balances and never offset one another."
                    },
                    "minutes": {
                        "type": "integer",
                        "description": "The signed balance delta in minutes. Negative consumes balance (a leave request); positive grants it (a manager's adjustment, which requires the `furlough::manage` permission to write). The API never computes this from the employee's working week or the organisation's free days: send the exact number of minutes you mean."
                    },
                    "start": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Start of the period this mutation covers, in UTC. Equal to `end` on a manual balance adjustment, which covers no period at all."
                    },
                    "end": {
                        "type": "string",
                        "format": "date-time",
                        "description": "End of the period this mutation covers, in UTC. Never before `start`, and equal to it on a manual balance adjustment."
                    },
                    "description": {
                        "type": "string",
                        "x-plnnrly-multiline": true,
                        "description": "The employee's or manager's own words for this mutation: the reason for the leave, or what the adjustment is for. The only field `?q=` searches."
                    },
                    "is_approved": {
                        "type": [
                            "boolean",
                            "null"
                        ],
                        "description": "Approval state, in three values: `null` means pending approval, `true` approved, `false` denied. The balance counts pending and approved mutations alike and ignores denied ones. Writing this field at all requires the `furlough::manage` permission."
                    },
                    "user": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/UserSummary"
                            }
                        ],
                        "description": "The employee this mutation belongs to. Expanding it with `?include=user` additionally requires the `users::read` permission and a token scope covering the users prefix."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "links": {
                        "$ref": "#/components/schemas/FurloughBalanceMutationLinks"
                    }
                },
                "required": [
                    "id",
                    "user_id",
                    "acceptor_id",
                    "year",
                    "type",
                    "minutes",
                    "start",
                    "end",
                    "description",
                    "is_approved",
                    "user",
                    "created_at",
                    "updated_at",
                    "links"
                ],
                "description": "A furlough balance mutation: one signed ledger entry against an employee's leave balance for a `year` and a leave `type`. A negative `minutes` consumes balance and is a leave request over the period from `start` to `end`; a positive `minutes` grants balance and is an adjustment a manager posts. `is_approved` is three-state and the `null` is semantic: `null` means pending approval, `true` approved, `false` denied. The balance counts the pending and the approved alike, never the denied, so a pending request already reduces what is left."
            },
            "FurloughBalanceMutationSummary": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "year": {
                        "type": "integer",
                        "description": "The balance year this mutation counts against."
                    },
                    "minutes": {
                        "type": "integer",
                        "description": "The signed balance delta in minutes; negative consumes balance, positive grants it."
                    }
                },
                "required": [
                    "id",
                    "year",
                    "minutes"
                ],
                "description": "A furlough balance mutation, as referenced from another resource."
            },
            "FurloughBalanceMutationsIndexMeta": {
                "type": "object",
                "properties": {
                    "page": {
                        "$ref": "#/components/schemas/PageOptionalTotal"
                    }
                },
                "additionalProperties": false,
                "required": [
                    "page"
                ]
            },
            "Holiday": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Name of the holiday, e.g. Nieuwjaarsdag."
                    },
                    "default_month": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Month the holiday normally falls in, 1-12. Null means the holiday moves from year to year (Easter, say), and the organisation fills the date in per year on the free day itself."
                    },
                    "default_day": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Day of the month the holiday normally falls on, 1-31, paired with `default_month`. Null means the day is not fixed."
                    },
                    "recurrence_interval": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "How often the holiday repeats, in years: `null` is not recurring, 1 is yearly, and n repeats every n years."
                    },
                    "sort_order": {
                        "type": "integer",
                        "description": "Position in the organisation's holiday list, ascending. Set by dragging holidays into order in plnnrly; the API exposes no way to reorder holidays.",
                        "x-plnnrly-server-assigned": true
                    },
                    "free_days_count": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Number of free days generated from this holiday."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "links": {
                        "$ref": "#/components/schemas/HolidayLinks"
                    }
                },
                "required": [
                    "id",
                    "name",
                    "default_month",
                    "default_day",
                    "recurrence_interval",
                    "sort_order",
                    "free_days_count",
                    "created_at",
                    "updated_at",
                    "links"
                ],
                "description": "A holiday: a recurring named day off, such as Nieuwjaarsdag. A holiday carries no calendar date of its own: it is a template whose actual dates live on the free days generated from it, so the same holiday serves every year."
            },
            "HolidayLinks": {
                "type": "object",
                "properties": {
                    "self": {
                        "type": "string",
                        "format": "uri",
                        "description": "The canonical URI of this record."
                    },
                    "free_days": {
                        "type": "string",
                        "format": "uri",
                        "description": "Free days generated from this holiday."
                    }
                },
                "required": [
                    "self",
                    "free_days"
                ],
                "additionalProperties": false
            },
            "HolidayList": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Name of the holiday, e.g. Nieuwjaarsdag."
                    },
                    "default_month": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Month the holiday normally falls in, 1-12. Null means the holiday moves from year to year (Easter, say), and the organisation fills the date in per year on the free day itself."
                    },
                    "default_day": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Day of the month the holiday normally falls on, 1-31, paired with `default_month`. Null means the day is not fixed."
                    },
                    "recurrence_interval": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "How often the holiday repeats, in years: `null` is not recurring, 1 is yearly, and n repeats every n years."
                    },
                    "sort_order": {
                        "type": "integer",
                        "description": "Position in the organisation's holiday list, ascending. Set by dragging holidays into order in plnnrly; the API exposes no way to reorder holidays.",
                        "x-plnnrly-server-assigned": true
                    },
                    "free_days_count": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Number of free days generated from this holiday."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "links": {
                        "$ref": "#/components/schemas/HolidayLinks"
                    }
                },
                "required": [
                    "id",
                    "name",
                    "default_month",
                    "default_day",
                    "recurrence_interval",
                    "sort_order",
                    "free_days_count",
                    "created_at",
                    "updated_at",
                    "links"
                ],
                "description": "A holiday: a recurring named day off, such as Nieuwjaarsdag. A holiday carries no calendar date of its own: it is a template whose actual dates live on the free days generated from it, so the same holiday serves every year."
            },
            "HolidaySummary": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Holiday name as shown in the UI."
                    }
                },
                "required": [
                    "id",
                    "name"
                ],
                "description": "A holiday, as referenced from another resource."
            },
            "HolidaysIndexMeta": {
                "type": "object",
                "properties": {
                    "page": {
                        "$ref": "#/components/schemas/Page"
                    }
                },
                "additionalProperties": false,
                "required": [
                    "page"
                ]
            },
            "IdempotencyKeyInFlightProblem": {
                "type": "object",
                "properties": {
                    "type": {
                        "type": "string",
                        "format": "uri",
                        "const": "https://developers.plnnrly.com/api/v2/problems/idempotency-key-in-flight",
                        "description": "The problem type. This is the only member a client switches on."
                    },
                    "title": {
                        "type": "string",
                        "description": "Short, stable per type, English, never interpolated. Never parse it."
                    },
                    "status": {
                        "type": "integer",
                        "const": 409,
                        "description": "The HTTP status, repeated in the body."
                    },
                    "detail": {
                        "type": "string",
                        "description": "Human-readable explanation. May quote values from your request; never shows internal error output."
                    },
                    "instance": {
                        "type": "string",
                        "description": "The request path, with no query string."
                    },
                    "request_id": {
                        "type": "string",
                        "description": "The id also in the X-Request-Id header. Quote it when you report a problem to plnnrly support."
                    }
                },
                "required": [
                    "type",
                    "title",
                    "status",
                    "detail",
                    "instance",
                    "request_id"
                ],
                "description": "Idempotency key in flight"
            },
            "IdempotencyKeyRequiredProblem": {
                "type": "object",
                "properties": {
                    "type": {
                        "type": "string",
                        "format": "uri",
                        "const": "https://developers.plnnrly.com/api/v2/problems/idempotency-key-required",
                        "description": "The problem type. This is the only member a client switches on."
                    },
                    "title": {
                        "type": "string",
                        "description": "Short, stable per type, English, never interpolated. Never parse it."
                    },
                    "status": {
                        "type": "integer",
                        "const": 400,
                        "description": "The HTTP status, repeated in the body."
                    },
                    "detail": {
                        "type": "string",
                        "description": "Human-readable explanation. May quote values from your request; never shows internal error output."
                    },
                    "instance": {
                        "type": "string",
                        "description": "The request path, with no query string."
                    },
                    "request_id": {
                        "type": "string",
                        "description": "The id also in the X-Request-Id header. Quote it when you report a problem to plnnrly support."
                    }
                },
                "required": [
                    "type",
                    "title",
                    "status",
                    "detail",
                    "instance",
                    "request_id"
                ],
                "description": "Idempotency key required"
            },
            "IdempotencyKeyReusedProblem": {
                "type": "object",
                "properties": {
                    "type": {
                        "type": "string",
                        "format": "uri",
                        "const": "https://developers.plnnrly.com/api/v2/problems/idempotency-key-reused",
                        "description": "The problem type. This is the only member a client switches on."
                    },
                    "title": {
                        "type": "string",
                        "description": "Short, stable per type, English, never interpolated. Never parse it."
                    },
                    "status": {
                        "type": "integer",
                        "const": 409,
                        "description": "The HTTP status, repeated in the body."
                    },
                    "detail": {
                        "type": "string",
                        "description": "Human-readable explanation. May quote values from your request; never shows internal error output."
                    },
                    "instance": {
                        "type": "string",
                        "description": "The request path, with no query string."
                    },
                    "request_id": {
                        "type": "string",
                        "description": "The id also in the X-Request-Id header. Quote it when you report a problem to plnnrly support."
                    }
                },
                "required": [
                    "type",
                    "title",
                    "status",
                    "detail",
                    "instance",
                    "request_id"
                ],
                "description": "Idempotency key reused"
            },
            "InsufficientPermissionProblem": {
                "type": "object",
                "properties": {
                    "type": {
                        "type": "string",
                        "format": "uri",
                        "const": "https://developers.plnnrly.com/api/v2/problems/insufficient-permission",
                        "description": "The problem type. This is the only member a client switches on."
                    },
                    "title": {
                        "type": "string",
                        "description": "Short, stable per type, English, never interpolated. Never parse it."
                    },
                    "status": {
                        "type": "integer",
                        "const": 403,
                        "description": "The HTTP status, repeated in the body."
                    },
                    "detail": {
                        "type": "string",
                        "description": "Human-readable explanation. May quote values from your request; never shows internal error output."
                    },
                    "instance": {
                        "type": "string",
                        "description": "The request path, with no query string."
                    },
                    "request_id": {
                        "type": "string",
                        "description": "The id also in the X-Request-Id header. Quote it when you report a problem to plnnrly support."
                    },
                    "required_permission": {
                        "type": "string",
                        "description": "The permission the user is missing, e.g. `projects::read`."
                    }
                },
                "required": [
                    "type",
                    "title",
                    "status",
                    "detail",
                    "instance",
                    "request_id"
                ],
                "description": "Insufficient permission"
            },
            "InsufficientTokenScopeProblem": {
                "type": "object",
                "properties": {
                    "type": {
                        "type": "string",
                        "format": "uri",
                        "const": "https://developers.plnnrly.com/api/v2/problems/insufficient-token-scope",
                        "description": "The problem type. This is the only member a client switches on."
                    },
                    "title": {
                        "type": "string",
                        "description": "Short, stable per type, English, never interpolated. Never parse it."
                    },
                    "status": {
                        "type": "integer",
                        "const": 403,
                        "description": "The HTTP status, repeated in the body."
                    },
                    "detail": {
                        "type": "string",
                        "description": "Human-readable explanation. May quote values from your request; never shows internal error output."
                    },
                    "instance": {
                        "type": "string",
                        "description": "The request path, with no query string."
                    },
                    "request_id": {
                        "type": "string",
                        "description": "The id also in the X-Request-Id header. Quote it when you report a problem to plnnrly support."
                    },
                    "required_ability": {
                        "type": "array",
                        "items": {
                            "type": "string"
                        },
                        "description": "The token abilities, any one of which would have permitted this request, e.g. `[\"api:read:project_drive\", \"api:read:customer_drive\"]`. A `*`-prefix route lists every ability of the required class."
                    }
                },
                "required": [
                    "type",
                    "title",
                    "status",
                    "detail",
                    "instance",
                    "request_id"
                ],
                "description": "Insufficient token scope"
            },
            "InternalErrorProblem": {
                "type": "object",
                "properties": {
                    "type": {
                        "type": "string",
                        "format": "uri",
                        "const": "https://developers.plnnrly.com/api/v2/problems/internal-error",
                        "description": "The problem type. This is the only member a client switches on."
                    },
                    "title": {
                        "type": "string",
                        "description": "Short, stable per type, English, never interpolated. Never parse it."
                    },
                    "status": {
                        "type": "integer",
                        "const": 500,
                        "description": "The HTTP status, repeated in the body."
                    },
                    "detail": {
                        "type": "string",
                        "description": "Human-readable explanation. May quote values from your request; never shows internal error output."
                    },
                    "instance": {
                        "type": "string",
                        "description": "The request path, with no query string."
                    },
                    "request_id": {
                        "type": "string",
                        "description": "The id also in the X-Request-Id header. Quote it when you report a problem to plnnrly support."
                    }
                },
                "required": [
                    "type",
                    "title",
                    "status",
                    "detail",
                    "instance",
                    "request_id"
                ],
                "description": "Internal error"
            },
            "InvalidCursorProblem": {
                "type": "object",
                "properties": {
                    "type": {
                        "type": "string",
                        "format": "uri",
                        "const": "https://developers.plnnrly.com/api/v2/problems/invalid-cursor",
                        "description": "The problem type. This is the only member a client switches on."
                    },
                    "title": {
                        "type": "string",
                        "description": "Short, stable per type, English, never interpolated. Never parse it."
                    },
                    "status": {
                        "type": "integer",
                        "const": 400,
                        "description": "The HTTP status, repeated in the body."
                    },
                    "detail": {
                        "type": "string",
                        "description": "Human-readable explanation. May quote values from your request; never shows internal error output."
                    },
                    "instance": {
                        "type": "string",
                        "description": "The request path, with no query string."
                    },
                    "request_id": {
                        "type": "string",
                        "description": "The id also in the X-Request-Id header. Quote it when you report a problem to plnnrly support."
                    }
                },
                "required": [
                    "type",
                    "title",
                    "status",
                    "detail",
                    "instance",
                    "request_id"
                ],
                "description": "Invalid cursor"
            },
            "InvalidQueryParameterProblem": {
                "type": "object",
                "properties": {
                    "type": {
                        "type": "string",
                        "format": "uri",
                        "const": "https://developers.plnnrly.com/api/v2/problems/invalid-query-parameter",
                        "description": "The problem type. This is the only member a client switches on."
                    },
                    "title": {
                        "type": "string",
                        "description": "Short, stable per type, English, never interpolated. Never parse it."
                    },
                    "status": {
                        "type": "integer",
                        "const": 400,
                        "description": "The HTTP status, repeated in the body."
                    },
                    "detail": {
                        "type": "string",
                        "description": "Human-readable explanation. May quote values from your request; never shows internal error output."
                    },
                    "instance": {
                        "type": "string",
                        "description": "The request path, with no query string."
                    },
                    "request_id": {
                        "type": "string",
                        "description": "The id also in the X-Request-Id header. Quote it when you report a problem to plnnrly support."
                    },
                    "errors": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "pointer": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "description": "RFC 6901 pointer into the request body, e.g. `/minutes`."
                                },
                                "parameter": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "description": "The query-parameter name, brackets included, e.g. `filter[start_date][gte]`."
                                },
                                "code": {
                                    "type": "string",
                                    "enum": [
                                        "unknown_parameter",
                                        "unsupported_parameter",
                                        "unknown_filter",
                                        "unknown_operator",
                                        "unknown_sort_key",
                                        "unknown_include_path",
                                        "depth_exceeded",
                                        "count_exceeded",
                                        "out_of_range",
                                        "invalid_format",
                                        "paired_filter_missing",
                                        "mutually_exclusive",
                                        "required",
                                        "integer",
                                        "numeric",
                                        "string",
                                        "boolean",
                                        "array",
                                        "min",
                                        "max",
                                        "between",
                                        "size",
                                        "distinct",
                                        "date_format",
                                        "decimal",
                                        "email",
                                        "url",
                                        "uuid",
                                        "regex",
                                        "after",
                                        "after_or_equal",
                                        "before",
                                        "exists",
                                        "unique",
                                        "required_with",
                                        "prohibited",
                                        "in"
                                    ],
                                    "x-extensible-enum": true,
                                    "description": "Machine-readable fault code. Switch on this, never on detail."
                                },
                                "detail": {
                                    "type": "string",
                                    "description": "Human-readable explanation; enumerates the allowed set for an allow-list failure."
                                }
                            },
                            "required": [
                                "code",
                                "detail"
                            ]
                        },
                        "description": "One entry per fault. Each carries exactly one of `pointer` or `parameter`."
                    }
                },
                "required": [
                    "type",
                    "title",
                    "status",
                    "detail",
                    "instance",
                    "request_id"
                ],
                "description": "Invalid query parameter"
            },
            "Invoice": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "project_id": {
                        "type": "integer",
                        "description": "Project this invoice was raised for."
                    },
                    "project": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/ProjectSummary"
                            }
                        ],
                        "description": "The project this invoice was raised for."
                    },
                    "user_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Employee the invoice is attributed to. Assigned from the authenticated user on create and never writable; `null` once that user is deleted.",
                        "x-plnnrly-server-assigned": true
                    },
                    "user": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/UserSummary"
                            },
                            {
                                "type": "null"
                            }
                        ],
                        "description": "The employee this invoice is attributed to. Expanding it with `?include=user` additionally requires the `users::read` permission and a token scope covering the users prefix."
                    },
                    "description": {
                        "type": "string",
                        "x-plnnrly-multiline": true,
                        "description": "What was invoiced, as free text. This is the field `?q=` searches."
                    },
                    "date": {
                        "type": "string",
                        "format": "date",
                        "description": "Invoice date, a zone-free calendar date. Newest first is the default sort."
                    },
                    "hourly_rate": {
                        "type": "string",
                        "format": "decimal",
                        "pattern": "^\\d+(\\.\\d{1,2})?$",
                        "x-plnnrly-minimum": 0,
                        "description": "Hourly rate this invoice was billed at, as a decimal string with two places. Never rounded or rescaled."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync. It also feeds the `ETag` revision, because a legacy invoice has no `version` field."
                    },
                    "links": {
                        "$ref": "#/components/schemas/InvoiceLinks"
                    }
                },
                "required": [
                    "id",
                    "project_id",
                    "project",
                    "user_id",
                    "user",
                    "description",
                    "date",
                    "hourly_rate",
                    "created_at",
                    "updated_at",
                    "links"
                ],
                "description": "A legacy invoice header: the description, date and hourly rate a project was billed at before sales invoices replaced it. It can be listed, read and created, but never edited or deleted through the API: both are always refused. Its rows are not exposed in any form, so there is no line-item total here."
            },
            "InvoiceLinks": {
                "type": "object",
                "properties": {
                    "self": {
                        "type": "string",
                        "format": "uri",
                        "description": "The canonical URI of this record."
                    }
                },
                "required": [
                    "self"
                ],
                "additionalProperties": false
            },
            "InvoiceList": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "project_id": {
                        "type": "integer",
                        "description": "Project this invoice was raised for."
                    },
                    "project": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/ProjectSummary"
                            }
                        ],
                        "description": "The project this invoice was raised for."
                    },
                    "user_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Employee the invoice is attributed to. Assigned from the authenticated user on create and never writable; `null` once that user is deleted.",
                        "x-plnnrly-server-assigned": true
                    },
                    "user": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/UserSummary"
                            },
                            {
                                "type": "null"
                            }
                        ],
                        "description": "The employee this invoice is attributed to. Expanding it with `?include=user` additionally requires the `users::read` permission and a token scope covering the users prefix."
                    },
                    "description": {
                        "type": "string",
                        "x-plnnrly-multiline": true,
                        "description": "What was invoiced, as free text. This is the field `?q=` searches."
                    },
                    "date": {
                        "type": "string",
                        "format": "date",
                        "description": "Invoice date, a zone-free calendar date. Newest first is the default sort."
                    },
                    "hourly_rate": {
                        "type": "string",
                        "format": "decimal",
                        "pattern": "^\\d+(\\.\\d{1,2})?$",
                        "x-plnnrly-minimum": 0,
                        "description": "Hourly rate this invoice was billed at, as a decimal string with two places. Never rounded or rescaled."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync. It also feeds the `ETag` revision, because a legacy invoice has no `version` field."
                    },
                    "links": {
                        "$ref": "#/components/schemas/InvoiceLinks"
                    }
                },
                "required": [
                    "id",
                    "project_id",
                    "project",
                    "user_id",
                    "user",
                    "description",
                    "date",
                    "hourly_rate",
                    "created_at",
                    "updated_at",
                    "links"
                ],
                "description": "A legacy invoice header: the description, date and hourly rate a project was billed at before sales invoices replaced it. It can be listed, read and created, but never edited or deleted through the API: both are always refused. Its rows are not exposed in any form, so there is no line-item total here."
            },
            "InvoiceSummary": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "date": {
                        "type": "string",
                        "format": "date",
                        "description": "Invoice date, a zone-free calendar date."
                    },
                    "hourly_rate": {
                        "type": "string",
                        "format": "decimal",
                        "pattern": "^-?\\d+(\\.\\d{1,2})?$",
                        "description": "Hourly rate the invoice was billed at, as a decimal string."
                    }
                },
                "required": [
                    "id",
                    "date",
                    "hourly_rate"
                ],
                "description": "A legacy invoice, as referenced from another resource."
            },
            "InvoicesIndexMeta": {
                "type": "object",
                "properties": {
                    "page": {
                        "$ref": "#/components/schemas/Page"
                    }
                },
                "additionalProperties": false,
                "required": [
                    "page"
                ]
            },
            "Label": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "project_id": {
                        "type": "integer",
                        "description": "The project this label belongs to. Follow `GET /api/v2/projects/{project}` for its details; the project is not expandable from here."
                    },
                    "name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Label name as shown on the chip, e.g. `Blocked`."
                    },
                    "color": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Six-digit hex colour, e.g. `#ef4444`. Stored verbatim: a row written before the colour picker enforced the format reads back as it is stored."
                    },
                    "icon": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Icon name, e.g. `heroicon-o-tag`: not a URL and not markup. Null when the label has no icon."
                    },
                    "order": {
                        "type": "integer",
                        "description": "Display position within the project. Assigned on create as the next position in that project's label order, changed only by dragging in plnnrly, and not writable.",
                        "x-plnnrly-server-assigned": true
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "links": {
                        "$ref": "#/components/schemas/LabelLinks"
                    }
                },
                "required": [
                    "id",
                    "project_id",
                    "name",
                    "color",
                    "icon",
                    "order",
                    "created_at",
                    "updated_at",
                    "links"
                ],
                "description": "A label: a per-project tag work items are marked with, carrying the colour and the optional icon the backlog chips and the work item detail view show beside its name. Labels belong to one project and are attached to work items from the work-item side."
            },
            "LabelLinks": {
                "type": "object",
                "properties": {
                    "self": {
                        "type": "string",
                        "format": "uri",
                        "description": "The canonical URI of this record."
                    }
                },
                "required": [
                    "self"
                ],
                "additionalProperties": false
            },
            "LabelList": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "project_id": {
                        "type": "integer",
                        "description": "The project this label belongs to. Follow `GET /api/v2/projects/{project}` for its details; the project is not expandable from here."
                    },
                    "name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Label name as shown on the chip, e.g. `Blocked`."
                    },
                    "color": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Six-digit hex colour, e.g. `#ef4444`. Stored verbatim: a row written before the colour picker enforced the format reads back as it is stored."
                    },
                    "icon": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Icon name, e.g. `heroicon-o-tag`: not a URL and not markup. Null when the label has no icon."
                    },
                    "order": {
                        "type": "integer",
                        "description": "Display position within the project. Assigned on create as the next position in that project's label order, changed only by dragging in plnnrly, and not writable.",
                        "x-plnnrly-server-assigned": true
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "links": {
                        "$ref": "#/components/schemas/LabelLinks"
                    }
                },
                "required": [
                    "id",
                    "project_id",
                    "name",
                    "color",
                    "icon",
                    "order",
                    "created_at",
                    "updated_at",
                    "links"
                ],
                "description": "A label: a per-project tag work items are marked with, carrying the colour and the optional icon the backlog chips and the work item detail view show beside its name. Labels belong to one project and are attached to work items from the work-item side."
            },
            "LabelSummary": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Label name as shown on the chip."
                    },
                    "color": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Six-digit hex colour, e.g. `#ef4444`."
                    }
                },
                "required": [
                    "id",
                    "name",
                    "color"
                ],
                "description": "A label, as referenced from another resource."
            },
            "LabelSummaryInclude": {
                "type": "object",
                "properties": {
                    "data": {
                        "type": "array",
                        "items": {
                            "$ref": "#/components/schemas/LabelSummary"
                        },
                        "maxItems": 25
                    },
                    "meta": {
                        "type": "object",
                        "properties": {
                            "count": {
                                "type": "integer",
                                "minimum": 0
                            },
                            "truncated": {
                                "type": "boolean"
                            }
                        },
                        "required": [
                            "truncated"
                        ],
                        "additionalProperties": false
                    }
                },
                "required": [
                    "data",
                    "meta"
                ],
                "additionalProperties": false
            },
            "LabelsIndexMeta": {
                "type": "object",
                "properties": {
                    "page": {
                        "$ref": "#/components/schemas/Page"
                    }
                },
                "additionalProperties": false,
                "required": [
                    "page"
                ]
            },
            "MalformedRequestProblem": {
                "type": "object",
                "properties": {
                    "type": {
                        "type": "string",
                        "format": "uri",
                        "const": "https://developers.plnnrly.com/api/v2/problems/malformed-request",
                        "description": "The problem type. This is the only member a client switches on."
                    },
                    "title": {
                        "type": "string",
                        "description": "Short, stable per type, English, never interpolated. Never parse it."
                    },
                    "status": {
                        "type": "integer",
                        "const": 400,
                        "description": "The HTTP status, repeated in the body."
                    },
                    "detail": {
                        "type": "string",
                        "description": "Human-readable explanation. May quote values from your request; never shows internal error output."
                    },
                    "instance": {
                        "type": "string",
                        "description": "The request path, with no query string."
                    },
                    "request_id": {
                        "type": "string",
                        "description": "The id also in the X-Request-Id header. Quote it when you report a problem to plnnrly support."
                    }
                },
                "required": [
                    "type",
                    "title",
                    "status",
                    "detail",
                    "instance",
                    "request_id"
                ],
                "description": "Malformed request"
            },
            "Media": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "File name as uploaded."
                    },
                    "mime_type": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "IANA media type."
                    },
                    "size": {
                        "type": "integer",
                        "description": "Size in bytes."
                    },
                    "disk": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Name of the storage location the file is kept in."
                    },
                    "collection": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Media collection the file belongs to."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Upload timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "url": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Signed download URL, requiring your bearer token. Request with `?include=url`. Null only where your organisation's file storage cannot sign URLs, in which case the response carries a `signed_url_unavailable` warning and downloads are not available through the API.",
                        "x-plnnrly-on-demand": true
                    },
                    "url_expires_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "When `url` stops working. Always `null` in v2.0: the signed route the URL points at does not expire. Declared so a future bounded-TTL download path is an additive change.",
                        "x-plnnrly-on-demand": true
                    }
                },
                "required": [
                    "id",
                    "name",
                    "mime_type",
                    "size",
                    "disk",
                    "collection",
                    "created_at",
                    "updated_at"
                ],
                "description": "A file attached to a record: the metadata of one upload (its name, type, size and the collection it belongs to) plus an on-demand signed download URL. Files are reached only inside their parent's collection, `work-items/{work_item}/attachments` or `drive-items/{drive_item}/files`. The API never streams bytes: uploading is a two-step flow, where you create an upload ticket on the parent, PUT the raw bytes to the ticket's own signed URL outside this API, then POST the ticket's id as `upload_id` to the collection. That way `/api/v2` still accepts exactly one request content type, `application/json`.",
                "x-plnnrly-omitted": {
                    "conversions_disk": "Used only by plnnrly's file storage.",
                    "custom_properties": "Used only by plnnrly's file storage.",
                    "file_name": "Storage file name; use `name`.",
                    "generated_conversions": "Used only by plnnrly's file storage.",
                    "manipulations": "Used only by plnnrly's file storage.",
                    "model_id": "The other half of that parent reference, withheld with it.",
                    "model_type": "A file is only ever reached inside its parent's collection, so the parent is the path rather than a payload field.",
                    "order_column": "The file's position as arranged in plnnrly, which the API does not use.",
                    "responsive_images": "Used only by plnnrly's file storage.",
                    "uuid": "A second identifier used only by plnnrly's file storage; `id` is the one to use."
                }
            },
            "MediaList": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "File name as uploaded."
                    },
                    "mime_type": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "IANA media type."
                    },
                    "size": {
                        "type": "integer",
                        "description": "Size in bytes."
                    },
                    "disk": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Name of the storage location the file is kept in."
                    },
                    "collection": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Media collection the file belongs to."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Upload timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "url": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Signed download URL, requiring your bearer token. Request with `?include=url`. Null only where your organisation's file storage cannot sign URLs, in which case the response carries a `signed_url_unavailable` warning and downloads are not available through the API.",
                        "x-plnnrly-on-demand": true
                    },
                    "url_expires_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "When `url` stops working. Always `null` in v2.0: the signed route the URL points at does not expire. Declared so a future bounded-TTL download path is an additive change.",
                        "x-plnnrly-on-demand": true
                    }
                },
                "required": [
                    "id",
                    "name",
                    "mime_type",
                    "size",
                    "disk",
                    "collection",
                    "created_at",
                    "updated_at"
                ],
                "description": "A file attached to a record: the metadata of one upload (its name, type, size and the collection it belongs to) plus an on-demand signed download URL. Files are reached only inside their parent's collection, `work-items/{work_item}/attachments` or `drive-items/{drive_item}/files`. The API never streams bytes: uploading is a two-step flow, where you create an upload ticket on the parent, PUT the raw bytes to the ticket's own signed URL outside this API, then POST the ticket's id as `upload_id` to the collection. That way `/api/v2` still accepts exactly one request content type, `application/json`."
            },
            "MediaSummary": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "File name as uploaded."
                    },
                    "mime_type": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "IANA media type."
                    }
                },
                "required": [
                    "id",
                    "name",
                    "mime_type"
                ],
                "description": "A file, as referenced from another resource."
            },
            "MethodNotAllowedProblem": {
                "type": "object",
                "properties": {
                    "type": {
                        "type": "string",
                        "format": "uri",
                        "const": "https://developers.plnnrly.com/api/v2/problems/method-not-allowed",
                        "description": "The problem type. This is the only member a client switches on."
                    },
                    "title": {
                        "type": "string",
                        "description": "Short, stable per type, English, never interpolated. Never parse it."
                    },
                    "status": {
                        "type": "integer",
                        "const": 405,
                        "description": "The HTTP status, repeated in the body."
                    },
                    "detail": {
                        "type": "string",
                        "description": "Human-readable explanation. May quote values from your request; never shows internal error output."
                    },
                    "instance": {
                        "type": "string",
                        "description": "The request path, with no query string."
                    },
                    "request_id": {
                        "type": "string",
                        "description": "The id also in the X-Request-Id header. Quote it when you report a problem to plnnrly support."
                    }
                },
                "required": [
                    "type",
                    "title",
                    "status",
                    "detail",
                    "instance",
                    "request_id"
                ],
                "description": "Method not allowed"
            },
            "NotAcceptableProblem": {
                "type": "object",
                "properties": {
                    "type": {
                        "type": "string",
                        "format": "uri",
                        "const": "https://developers.plnnrly.com/api/v2/problems/not-acceptable",
                        "description": "The problem type. This is the only member a client switches on."
                    },
                    "title": {
                        "type": "string",
                        "description": "Short, stable per type, English, never interpolated. Never parse it."
                    },
                    "status": {
                        "type": "integer",
                        "const": 406,
                        "description": "The HTTP status, repeated in the body."
                    },
                    "detail": {
                        "type": "string",
                        "description": "Human-readable explanation. May quote values from your request; never shows internal error output."
                    },
                    "instance": {
                        "type": "string",
                        "description": "The request path, with no query string."
                    },
                    "request_id": {
                        "type": "string",
                        "description": "The id also in the X-Request-Id header. Quote it when you report a problem to plnnrly support."
                    }
                },
                "required": [
                    "type",
                    "title",
                    "status",
                    "detail",
                    "instance",
                    "request_id"
                ],
                "description": "Not acceptable"
            },
            "Note": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "content": {
                        "type": "string",
                        "format": "html",
                        "description": "Note body as rich-text HTML, sanitised on read."
                    },
                    "order_column": {
                        "type": "integer",
                        "description": "Sort position within the parent record. Assigned by the server; reordering is not exposed in v2.0. Positions are not counted per parent, so the sequence is effectively creation order across the whole organisation.",
                        "x-plnnrly-server-assigned": true
                    },
                    "user_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Author of the note. Always the authenticated user at create time; `null` where the author has since been deleted.",
                        "x-plnnrly-server-assigned": true
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "notable_type": {
                        "type": "string",
                        "enum": [
                            "project",
                            "customer",
                            "work_item"
                        ],
                        "x-extensible-enum": true,
                        "description": "The kind of record `notable` points at; it says which branch of `notable` applies."
                    },
                    "notable_id": {
                        "type": "integer",
                        "description": "The id of the record `notable` points at."
                    },
                    "notable": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/ProjectSummary"
                            },
                            {
                                "$ref": "#/components/schemas/CustomerSummary"
                            },
                            {
                                "$ref": "#/components/schemas/WorkItemSummary"
                            }
                        ],
                        "x-plnnrly-discriminator": {
                            "propertyName": "notable_type",
                            "mapping": {
                                "project": "#/components/schemas/ProjectSummary",
                                "customer": "#/components/schemas/CustomerSummary",
                                "work_item": "#/components/schemas/WorkItemSummary"
                            }
                        },
                        "x-plnnrly-morph-aliases": [
                            "project",
                            "customer",
                            "work_item"
                        ],
                        "description": "The project, customer or work item this note is attached to."
                    },
                    "user": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/UserSummary"
                            },
                            {
                                "type": "null"
                            }
                        ],
                        "description": "Author of the note."
                    },
                    "links": {
                        "$ref": "#/components/schemas/NoteLinks"
                    }
                },
                "required": [
                    "id",
                    "content",
                    "order_column",
                    "user_id",
                    "created_at",
                    "updated_at",
                    "notable_type",
                    "notable_id",
                    "notable",
                    "user",
                    "links"
                ],
                "description": "A free-text note attached to a project, a customer or a work item: the running commentary plnnrly shows in a record's Notes sub-page. The body is rich text from the editor, sanitised on the way out. A note is read through the record it hangs off, and written under that record's own note permission: `projects::notes`, `customers::update` or `project_management::manage_work_items` by parent type."
            },
            "NoteLinks": {
                "type": "object",
                "properties": {
                    "self": {
                        "type": "string",
                        "format": "uri",
                        "description": "The canonical URI of this record."
                    }
                },
                "required": [
                    "self"
                ],
                "additionalProperties": false
            },
            "NoteList": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "content": {
                        "type": "string",
                        "format": "html",
                        "description": "Note body as rich-text HTML, sanitised on read."
                    },
                    "order_column": {
                        "type": "integer",
                        "description": "Sort position within the parent record. Assigned by the server; reordering is not exposed in v2.0. Positions are not counted per parent, so the sequence is effectively creation order across the whole organisation.",
                        "x-plnnrly-server-assigned": true
                    },
                    "user_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Author of the note. Always the authenticated user at create time; `null` where the author has since been deleted.",
                        "x-plnnrly-server-assigned": true
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "notable_type": {
                        "type": "string",
                        "enum": [
                            "project",
                            "customer",
                            "work_item"
                        ],
                        "x-extensible-enum": true,
                        "description": "The kind of record `notable` points at; it says which branch of `notable` applies."
                    },
                    "notable_id": {
                        "type": "integer",
                        "description": "The id of the record `notable` points at."
                    },
                    "notable": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/ProjectSummary"
                            },
                            {
                                "$ref": "#/components/schemas/CustomerSummary"
                            },
                            {
                                "$ref": "#/components/schemas/WorkItemSummary"
                            }
                        ],
                        "x-plnnrly-discriminator": {
                            "propertyName": "notable_type",
                            "mapping": {
                                "project": "#/components/schemas/ProjectSummary",
                                "customer": "#/components/schemas/CustomerSummary",
                                "work_item": "#/components/schemas/WorkItemSummary"
                            }
                        },
                        "x-plnnrly-morph-aliases": [
                            "project",
                            "customer",
                            "work_item"
                        ],
                        "description": "The project, customer or work item this note is attached to."
                    },
                    "user": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/UserSummary"
                            },
                            {
                                "type": "null"
                            }
                        ],
                        "description": "Author of the note."
                    },
                    "links": {
                        "$ref": "#/components/schemas/NoteLinks"
                    }
                },
                "required": [
                    "id",
                    "content",
                    "order_column",
                    "user_id",
                    "created_at",
                    "updated_at",
                    "notable_type",
                    "notable_id",
                    "notable",
                    "user",
                    "links"
                ],
                "description": "A free-text note attached to a project, a customer or a work item: the running commentary plnnrly shows in a record's Notes sub-page. The body is rich text from the editor, sanitised on the way out. A note is read through the record it hangs off, and written under that record's own note permission: `projects::notes`, `customers::update` or `project_management::manage_work_items` by parent type."
            },
            "NotesIndexMeta": {
                "type": "object",
                "properties": {
                    "page": {
                        "$ref": "#/components/schemas/PageOptionalTotal"
                    }
                },
                "additionalProperties": false,
                "required": [
                    "page"
                ]
            },
            "Page": {
                "type": "object",
                "properties": {
                    "per_page": {
                        "type": "integer",
                        "description": "The page size that was applied."
                    },
                    "next_cursor": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Cursor for the next page, or null on the last one. Always null in page mode."
                    },
                    "prev_cursor": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Cursor for the previous page, or null on the first one. Always null in page mode."
                    },
                    "total": {
                        "type": "integer",
                        "description": "The total number of matching rows."
                    },
                    "current_page": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "The page number this response answers, echoed from the request even when it exceeds last_page. Null when the request did not send page."
                    },
                    "last_page": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "The highest page number that holds rows: ceil(total / per_page), and 1 on an empty collection. Null when the request did not send page."
                    }
                },
                "required": [
                    "per_page",
                    "next_cursor",
                    "prev_cursor",
                    "total",
                    "current_page",
                    "last_page"
                ],
                "additionalProperties": false
            },
            "PageLinks": {
                "type": "object",
                "properties": {
                    "self": {
                        "type": "string",
                        "format": "uri",
                        "description": "This page."
                    },
                    "next": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "uri",
                        "description": "The next page, or null on the last one."
                    },
                    "prev": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "uri",
                        "description": "The previous page, or null on the first one."
                    }
                },
                "required": [
                    "self",
                    "next",
                    "prev"
                ],
                "additionalProperties": false
            },
            "PageOptionalTotal": {
                "type": "object",
                "properties": {
                    "per_page": {
                        "type": "integer",
                        "description": "The page size that was applied."
                    },
                    "next_cursor": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Cursor for the next page, or null on the last one."
                    },
                    "prev_cursor": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Cursor for the previous page, or null on the first one."
                    },
                    "total": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "The total number of matching rows; null unless with_total=1 was sent."
                    }
                },
                "required": [
                    "per_page",
                    "next_cursor",
                    "prev_cursor",
                    "total"
                ],
                "additionalProperties": false
            },
            "PreconditionFailedProblem": {
                "type": "object",
                "properties": {
                    "type": {
                        "type": "string",
                        "format": "uri",
                        "const": "https://developers.plnnrly.com/api/v2/problems/precondition-failed",
                        "description": "The problem type. This is the only member a client switches on."
                    },
                    "title": {
                        "type": "string",
                        "description": "Short, stable per type, English, never interpolated. Never parse it."
                    },
                    "status": {
                        "type": "integer",
                        "const": 412,
                        "description": "The HTTP status, repeated in the body."
                    },
                    "detail": {
                        "type": "string",
                        "description": "Human-readable explanation. May quote values from your request; never shows internal error output."
                    },
                    "instance": {
                        "type": "string",
                        "description": "The request path, with no query string."
                    },
                    "request_id": {
                        "type": "string",
                        "description": "The id also in the X-Request-Id header. Quote it when you report a problem to plnnrly support."
                    },
                    "current_version": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "The record's current version, where it has one. Null otherwise."
                    }
                },
                "required": [
                    "type",
                    "title",
                    "status",
                    "detail",
                    "instance",
                    "request_id"
                ],
                "description": "Precondition failed"
            },
            "Project": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "title": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Project title as shown in the UI."
                    },
                    "full_title": {
                        "type": "string",
                        "description": "Application name plus title. Never carries the project number, even when the organisation shows numbers in plnnrly."
                    },
                    "description": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "html",
                        "description": "Free-text description. HTML from the rich editor, sanitised."
                    },
                    "status": {
                        "type": "string",
                        "enum": [
                            "active",
                            "finished",
                            "invoiced",
                            "on-hold",
                            "inactive"
                        ],
                        "x-extensible-enum": true,
                        "description": "Lifecycle status. Setting it to invoiced finishes every task on every budget of the project."
                    },
                    "workflow_mode": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "enum": [
                            "sprints",
                            "simple",
                            null
                        ],
                        "x-extensible-enum": true,
                        "description": "Whether work is planned in sprints or on a simple board."
                    },
                    "start_date": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date",
                        "description": "First day of the project."
                    },
                    "end_date": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date",
                        "description": "Last day, `null` if open-ended."
                    },
                    "is_billable": {
                        "type": "boolean",
                        "description": "Whether time on this project is billable."
                    },
                    "is_continuous": {
                        "type": "boolean",
                        "description": "Whether the project has no planned end."
                    },
                    "last_activity": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "Most recent write anywhere in the project."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "deleted_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "Soft-delete timestamp, `null` while live."
                    },
                    "customer_id": {
                        "type": "integer",
                        "description": "Owning customer."
                    },
                    "sub_customer_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Optional sub-customer."
                    },
                    "project_manager_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Responsible project manager."
                    },
                    "account_manager_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Responsible account manager. Set to the creating user and not writable.",
                        "x-plnnrly-server-assigned": true
                    },
                    "application_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Linked application. Writable only while the organisation has the Infrastructure feature: sending a non-`null` value without it is a `422` `prohibited`. Null clears the link regardless."
                    },
                    "workflow_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Workflow whose statuses this project's work items move through."
                    },
                    "web_url": {
                        "type": "string",
                        "description": "Absolute URL that opens this project's work in plnnrly, on the organisation's own hostname. It is a stable link, safe to store: it redirects to the first of the project's backlog, all-work list or board the signed-in user may open, and to the project page when none of them applies. Read-only."
                    },
                    "customer": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/CustomerSummary"
                            }
                        ],
                        "description": "Owning customer."
                    },
                    "sub_customer": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/CustomerSummary"
                            },
                            {
                                "type": "null"
                            }
                        ],
                        "description": "Optional sub-customer."
                    },
                    "project_manager": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/UserSummary"
                            },
                            {
                                "type": "null"
                            }
                        ],
                        "description": "Responsible project manager."
                    },
                    "account_manager": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/UserSummary"
                            },
                            {
                                "type": "null"
                            }
                        ],
                        "description": "Responsible account manager."
                    },
                    "tags": {
                        "$ref": "#/components/schemas/TagSummaryInclude",
                        "description": "Tags on this project. A project takes at most 50 tags, so the include lists them all. `tag_ids` replaces the whole set.",
                        "x-plnnrly-include-only": true
                    },
                    "sprints_count": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Number of sprints."
                    },
                    "budgets_count": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Number of budgets."
                    },
                    "tags_count": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Number of tags."
                    },
                    "tasks_count": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Number of kanban tasks on this project that the caller may see."
                    },
                    "logged_minutes": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Sum of the worklog minutes on this project that the caller may see. Worklogs are per-user unless the caller holds `times::update_others` or `times::delete_others`. Null when the caller lacks `hours::read` or a token covering `api:read:times`.",
                        "x-plnnrly-on-demand": true
                    },
                    "workflow_mode_options": {
                        "type": "array",
                        "items": {
                            "type": "string",
                            "enum": [
                                "sprints",
                                "simple"
                            ],
                            "x-extensible-enum": true
                        },
                        "description": "Workflow modes this project may switch to. Empty until a workflow is linked."
                    },
                    "links": {
                        "$ref": "#/components/schemas/ProjectLinks"
                    }
                },
                "required": [
                    "id",
                    "title",
                    "full_title",
                    "description",
                    "status",
                    "workflow_mode",
                    "start_date",
                    "end_date",
                    "is_billable",
                    "is_continuous",
                    "last_activity",
                    "created_at",
                    "updated_at",
                    "deleted_at",
                    "customer_id",
                    "sub_customer_id",
                    "project_manager_id",
                    "account_manager_id",
                    "application_id",
                    "workflow_id",
                    "web_url",
                    "customer",
                    "sub_customer",
                    "project_manager",
                    "account_manager",
                    "sprints_count",
                    "budgets_count",
                    "tags_count",
                    "tasks_count",
                    "workflow_mode_options",
                    "links"
                ],
                "description": "A project: the unit customers are billed against. Carries a workflow mode that decides whether work is planned in sprints or on a continuous board."
            },
            "ProjectLink": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "project_id": {
                        "type": "integer",
                        "description": "Owning project."
                    },
                    "project": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/ProjectSummary"
                            }
                        ],
                        "description": "The project this link belongs to."
                    },
                    "name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Link name as shown in the UI, e.g. `Staging`. This is the default sort key."
                    },
                    "url": {
                        "type": "string",
                        "x-plnnrly-multiline": true,
                        "description": "The link target, e.g. `https://staging.example.test`."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "links": {
                        "$ref": "#/components/schemas/ProjectLinkLinks"
                    }
                },
                "required": [
                    "id",
                    "project_id",
                    "project",
                    "name",
                    "url",
                    "created_at",
                    "updated_at",
                    "links"
                ],
                "description": "A named external URL pinned to a project, such as a staging environment, a design file or a repository. Deleting one is permanent, and every write bumps the owning project's `updated_at` and lands in the project's activity timeline."
            },
            "ProjectLinkLinks": {
                "type": "object",
                "properties": {
                    "self": {
                        "type": "string",
                        "format": "uri",
                        "description": "The canonical URI of this record."
                    }
                },
                "required": [
                    "self"
                ],
                "additionalProperties": false
            },
            "ProjectLinkList": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "project_id": {
                        "type": "integer",
                        "description": "Owning project."
                    },
                    "project": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/ProjectSummary"
                            }
                        ],
                        "description": "The project this link belongs to."
                    },
                    "name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Link name as shown in the UI, e.g. `Staging`. This is the default sort key."
                    },
                    "url": {
                        "type": "string",
                        "x-plnnrly-multiline": true,
                        "description": "The link target, e.g. `https://staging.example.test`."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "links": {
                        "$ref": "#/components/schemas/ProjectLinkLinks"
                    }
                },
                "required": [
                    "id",
                    "project_id",
                    "project",
                    "name",
                    "url",
                    "created_at",
                    "updated_at",
                    "links"
                ],
                "description": "A named external URL pinned to a project, such as a staging environment, a design file or a repository. Deleting one is permanent, and every write bumps the owning project's `updated_at` and lands in the project's activity timeline."
            },
            "ProjectLinkSummary": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Link name as shown in the UI."
                    },
                    "url": {
                        "type": "string",
                        "x-plnnrly-multiline": true,
                        "description": "The link target."
                    }
                },
                "required": [
                    "id",
                    "name",
                    "url"
                ],
                "description": "A project link, as referenced from another resource."
            },
            "ProjectLinks": {
                "type": "object",
                "properties": {
                    "self": {
                        "type": "string",
                        "format": "uri",
                        "description": "The canonical URI of this record."
                    },
                    "tasks": {
                        "type": "string",
                        "format": "uri",
                        "description": "Kanban tasks on this project."
                    },
                    "budgets": {
                        "type": "string",
                        "format": "uri",
                        "description": "Budgets on this project."
                    },
                    "sprints": {
                        "type": "string",
                        "format": "uri",
                        "description": "Sprints on this project."
                    }
                },
                "required": [
                    "self",
                    "tasks",
                    "budgets",
                    "sprints"
                ],
                "additionalProperties": false
            },
            "ProjectLinksIndexMeta": {
                "type": "object",
                "properties": {
                    "page": {
                        "$ref": "#/components/schemas/Page"
                    }
                },
                "additionalProperties": false,
                "required": [
                    "page"
                ]
            },
            "ProjectList": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "title": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Project title as shown in the UI."
                    },
                    "full_title": {
                        "type": "string",
                        "description": "Application name plus title. Never carries the project number, even when the organisation shows numbers in plnnrly."
                    },
                    "status": {
                        "type": "string",
                        "enum": [
                            "active",
                            "finished",
                            "invoiced",
                            "on-hold",
                            "inactive"
                        ],
                        "x-extensible-enum": true,
                        "description": "Lifecycle status. Setting it to invoiced finishes every task on every budget of the project."
                    },
                    "workflow_mode": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "enum": [
                            "sprints",
                            "simple",
                            null
                        ],
                        "x-extensible-enum": true,
                        "description": "Whether work is planned in sprints or on a simple board."
                    },
                    "start_date": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date",
                        "description": "First day of the project."
                    },
                    "end_date": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date",
                        "description": "Last day, `null` if open-ended."
                    },
                    "is_billable": {
                        "type": "boolean",
                        "description": "Whether time on this project is billable."
                    },
                    "is_continuous": {
                        "type": "boolean",
                        "description": "Whether the project has no planned end."
                    },
                    "last_activity": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "Most recent write anywhere in the project."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "deleted_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "Soft-delete timestamp, `null` while live."
                    },
                    "customer_id": {
                        "type": "integer",
                        "description": "Owning customer."
                    },
                    "sub_customer_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Optional sub-customer."
                    },
                    "project_manager_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Responsible project manager."
                    },
                    "account_manager_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Responsible account manager. Set to the creating user and not writable.",
                        "x-plnnrly-server-assigned": true
                    },
                    "application_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Linked application. Writable only while the organisation has the Infrastructure feature: sending a non-`null` value without it is a `422` `prohibited`. Null clears the link regardless."
                    },
                    "workflow_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Workflow whose statuses this project's work items move through."
                    },
                    "web_url": {
                        "type": "string",
                        "description": "Absolute URL that opens this project's work in plnnrly, on the organisation's own hostname. It is a stable link, safe to store: it redirects to the first of the project's backlog, all-work list or board the signed-in user may open, and to the project page when none of them applies. Read-only."
                    },
                    "customer": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/CustomerSummary"
                            }
                        ],
                        "description": "Owning customer."
                    },
                    "sub_customer": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/CustomerSummary"
                            },
                            {
                                "type": "null"
                            }
                        ],
                        "description": "Optional sub-customer."
                    },
                    "project_manager": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/UserSummary"
                            },
                            {
                                "type": "null"
                            }
                        ],
                        "description": "Responsible project manager."
                    },
                    "account_manager": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/UserSummary"
                            },
                            {
                                "type": "null"
                            }
                        ],
                        "description": "Responsible account manager."
                    },
                    "tags": {
                        "$ref": "#/components/schemas/TagSummaryInclude",
                        "description": "Tags on this project. A project takes at most 50 tags, so the include lists them all. `tag_ids` replaces the whole set.",
                        "x-plnnrly-include-only": true
                    },
                    "sprints_count": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Number of sprints."
                    },
                    "budgets_count": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Number of budgets."
                    },
                    "tags_count": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Number of tags."
                    },
                    "tasks_count": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Number of kanban tasks on this project that the caller may see."
                    },
                    "logged_minutes": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Sum of the worklog minutes on this project that the caller may see. Worklogs are per-user unless the caller holds `times::update_others` or `times::delete_others`. Null when the caller lacks `hours::read` or a token covering `api:read:times`.",
                        "x-plnnrly-on-demand": true
                    },
                    "links": {
                        "$ref": "#/components/schemas/ProjectLinks"
                    }
                },
                "required": [
                    "id",
                    "title",
                    "full_title",
                    "status",
                    "workflow_mode",
                    "start_date",
                    "end_date",
                    "is_billable",
                    "is_continuous",
                    "last_activity",
                    "created_at",
                    "updated_at",
                    "deleted_at",
                    "customer_id",
                    "sub_customer_id",
                    "project_manager_id",
                    "account_manager_id",
                    "application_id",
                    "workflow_id",
                    "web_url",
                    "customer",
                    "sub_customer",
                    "project_manager",
                    "account_manager",
                    "sprints_count",
                    "budgets_count",
                    "tags_count",
                    "tasks_count",
                    "links"
                ],
                "description": "A project: the unit customers are billed against. Carries a workflow mode that decides whether work is planned in sprints or on a continuous board."
            },
            "ProjectSummary": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "title": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Project title as shown in the UI."
                    },
                    "customer_id": {
                        "type": "integer",
                        "description": "Customer the project belongs to."
                    }
                },
                "required": [
                    "id",
                    "title",
                    "customer_id"
                ],
                "description": "A project, as referenced from another resource."
            },
            "ProjectsActionsRestoreMeta": {
                "type": "object",
                "properties": {
                    "warnings": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "code": {
                                    "type": "string",
                                    "enum": [
                                        "already_in_target_state"
                                    ],
                                    "x-extensible-enum": true,
                                    "description": "Machine-readable warning code. Switch on this, never on detail."
                                },
                                "detail": {
                                    "type": "string",
                                    "description": "Human-readable explanation."
                                },
                                "pointer": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "description": "RFC 6901 pointer into the request body, where the warning is about a body field."
                                },
                                "parameter": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "description": "The query-parameter name, brackets included, where the warning is about a parameter."
                                },
                                "sunset": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "format": "date",
                                    "description": "Date the deprecated thing stops working."
                                },
                                "replacement": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "description": "What to use instead."
                                }
                            },
                            "required": [
                                "code",
                                "detail"
                            ],
                            "description": "An advisory about this request. It never changes the status code or `data`."
                        },
                        "description": "Non-fatal notes about this request. Switch on `code`, never on `detail`."
                    }
                },
                "additionalProperties": false
            },
            "ProjectsIndexMeta": {
                "type": "object",
                "properties": {
                    "page": {
                        "$ref": "#/components/schemas/Page"
                    },
                    "warnings": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "code": {
                                    "type": "string",
                                    "enum": [
                                        "include_truncated"
                                    ],
                                    "x-extensible-enum": true,
                                    "description": "Machine-readable warning code. Switch on this, never on detail."
                                },
                                "detail": {
                                    "type": "string",
                                    "description": "Human-readable explanation."
                                },
                                "pointer": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "description": "RFC 6901 pointer into the request body, where the warning is about a body field."
                                },
                                "parameter": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "description": "The query-parameter name, brackets included, where the warning is about a parameter."
                                },
                                "sunset": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "format": "date",
                                    "description": "Date the deprecated thing stops working."
                                },
                                "replacement": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "description": "What to use instead."
                                }
                            },
                            "required": [
                                "code",
                                "detail"
                            ],
                            "description": "An advisory about this request. It never changes the status code or `data`."
                        },
                        "description": "Non-fatal notes about this request. Switch on `code`, never on `detail`."
                    }
                },
                "additionalProperties": false,
                "required": [
                    "page"
                ]
            },
            "ProjectsShowMeta": {
                "type": "object",
                "properties": {
                    "actions": {
                        "type": "array",
                        "items": {
                            "type": "string",
                            "enum": [
                                "restore"
                            ]
                        },
                        "description": "The actions the caller may currently invoke on this record."
                    }
                },
                "additionalProperties": false
            },
            "ProjectsTasksIndexMeta": {
                "type": "object",
                "properties": {
                    "page": {
                        "$ref": "#/components/schemas/PageOptionalTotal"
                    }
                },
                "additionalProperties": false,
                "required": [
                    "page"
                ]
            },
            "RateLimitExceededProblem": {
                "type": "object",
                "properties": {
                    "type": {
                        "type": "string",
                        "format": "uri",
                        "const": "https://developers.plnnrly.com/api/v2/problems/rate-limit-exceeded",
                        "description": "The problem type. This is the only member a client switches on."
                    },
                    "title": {
                        "type": "string",
                        "description": "Short, stable per type, English, never interpolated. Never parse it."
                    },
                    "status": {
                        "type": "integer",
                        "const": 429,
                        "description": "The HTTP status, repeated in the body."
                    },
                    "detail": {
                        "type": "string",
                        "description": "Human-readable explanation. May quote values from your request; never shows internal error output."
                    },
                    "instance": {
                        "type": "string",
                        "description": "The request path, with no query string."
                    },
                    "request_id": {
                        "type": "string",
                        "description": "The id also in the X-Request-Id header. Quote it when you report a problem to plnnrly support."
                    },
                    "rate_limit": {
                        "type": "object",
                        "properties": {
                            "limit": {
                                "type": "integer",
                                "description": "Units permitted per minute in the exhausted bucket."
                            },
                            "retry_after": {
                                "type": "integer",
                                "description": "Seconds until the bucket refills."
                            }
                        },
                        "required": [
                            "limit",
                            "retry_after"
                        ],
                        "description": "Which ceiling was exhausted, and for how long."
                    }
                },
                "required": [
                    "type",
                    "title",
                    "status",
                    "detail",
                    "instance",
                    "request_id"
                ],
                "description": "Rate limit exceeded"
            },
            "Ref": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer"
                    }
                },
                "required": [
                    "id"
                ],
                "additionalProperties": false,
                "description": "A reference to a record by its id. The full object appears when the relation is included."
            },
            "ResourceNotFoundProblem": {
                "type": "object",
                "properties": {
                    "type": {
                        "type": "string",
                        "format": "uri",
                        "const": "https://developers.plnnrly.com/api/v2/problems/resource-not-found",
                        "description": "The problem type. This is the only member a client switches on."
                    },
                    "title": {
                        "type": "string",
                        "description": "Short, stable per type, English, never interpolated. Never parse it."
                    },
                    "status": {
                        "type": "integer",
                        "const": 404,
                        "description": "The HTTP status, repeated in the body."
                    },
                    "detail": {
                        "type": "string",
                        "description": "Human-readable explanation. May quote values from your request; never shows internal error output."
                    },
                    "instance": {
                        "type": "string",
                        "description": "The request path, with no query string."
                    },
                    "request_id": {
                        "type": "string",
                        "description": "The id also in the X-Request-Id header. Quote it when you report a problem to plnnrly support."
                    }
                },
                "required": [
                    "type",
                    "title",
                    "status",
                    "detail",
                    "instance",
                    "request_id"
                ],
                "description": "Resource not found"
            },
            "SalesInvoice": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "customer_id": {
                        "type": "integer",
                        "description": "Customer this invoice is billed to."
                    },
                    "customer": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/CustomerSummary"
                            }
                        ],
                        "description": "The customer this invoice is billed to. Expanding it with `?include=customer` additionally requires the `customers::read` permission and a token scope covering the customers prefix."
                    },
                    "project_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Project this invoice was raised for, when it was raised from one."
                    },
                    "customer_subscription_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Customer subscription this invoice was generated from, when it was generated from a renewal."
                    },
                    "invoice_number": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Invoice number, allocated by the server from the organisation's yearly sequence: B-26-00001 for an invoice raised in this flow, L-1234 for one converted from a legacy invoice. It also works in the path: `GET sales-invoices/B-26-00001` works. This is the field `?q=` searches.",
                        "x-plnnrly-server-assigned": true
                    },
                    "reference": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "The customer's own reference, typically a purchase-order number, reproduced on the invoice."
                    },
                    "description": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "One-line description of what was invoiced."
                    },
                    "invoice_date": {
                        "type": "string",
                        "format": "date",
                        "description": "Invoice date, a zone-free calendar date. Newest first is the default sort."
                    },
                    "due_date": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date",
                        "description": "Date payment is due, a zone-free calendar date. Null when no term was applied."
                    },
                    "status": {
                        "type": "string",
                        "enum": [
                            "draft",
                            "pending_approval",
                            "sent",
                            "paid",
                            "overdue",
                            "cancelled"
                        ],
                        "x-extensible-enum": true,
                        "description": "Lifecycle status. Resolved by the server on create and never writable: a caller without `sales_invoices::finalize` creates a `pending_approval` invoice, and any update by such a caller returns the invoice to `pending_approval`.",
                        "x-plnnrly-server-assigned": true
                    },
                    "should_email": {
                        "type": "boolean",
                        "description": "Whether the invoice is emailed to the customer when it syncs. The email is sent through the accounting integration, so it is forced to `false` in an organisation with no integration configured. Writing it requires `sales_invoices::finalize`."
                    },
                    "subtotal": {
                        "type": "string",
                        "format": "decimal",
                        "pattern": "^-?\\d+(\\.\\d{1,2})?$",
                        "description": "Sum of the row line totals excluding VAT, as a decimal string. Recomputed from the rows; never written directly.",
                        "x-plnnrly-server-assigned": true
                    },
                    "vat_total": {
                        "type": "string",
                        "format": "decimal",
                        "pattern": "^-?\\d+(\\.\\d{1,2})?$",
                        "description": "Sum of the row VAT amounts, as a decimal string. Recomputed from the rows; never written directly.",
                        "x-plnnrly-server-assigned": true
                    },
                    "total": {
                        "type": "string",
                        "format": "decimal",
                        "pattern": "^-?\\d+(\\.\\d{1,2})?$",
                        "description": "Subtotal plus VAT, as a decimal string. Recomputed from the rows; never written directly.",
                        "x-plnnrly-server-assigned": true
                    },
                    "exact_invoice_id": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "The accounting system's own identifier for this invoice, written by the sync. Not a local id.",
                        "x-plnnrly-server-assigned": true
                    },
                    "exact_journal": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Sales journal the invoice is booked in, resolved from the organisation's accounting settings on create. Null in an organisation with no integration configured.",
                        "x-plnnrly-server-assigned": true
                    },
                    "sync_status": {
                        "type": "string",
                        "enum": [
                            "pending",
                            "synced",
                            "failed",
                            "local"
                        ],
                        "x-extensible-enum": true,
                        "description": "How far the invoice has got through the accounting sync. Set on create: local in an organisation with no integration configured, pending otherwise.",
                        "x-plnnrly-server-assigned": true
                    },
                    "sync_error": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "x-plnnrly-multiline": true,
                        "description": "Why the last sync attempt failed, verbatim from the accounting system. Null while nothing has failed.",
                        "x-plnnrly-server-assigned": true
                    },
                    "synced_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "When the invoice last synced successfully.",
                        "x-plnnrly-server-assigned": true
                    },
                    "sync_attempted_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "When the invoice was last handed to the sync, successfully or not.",
                        "x-plnnrly-server-assigned": true
                    },
                    "paid_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "When the invoice was settled. Set on create for a local invoice that is immediately definitive; otherwise written when payment is registered.",
                        "x-plnnrly-server-assigned": true
                    },
                    "is_legacy": {
                        "type": "boolean",
                        "description": "Whether this invoice was converted from a legacy invoice. Such an invoice is read-only: PATCH and DELETE answer `409` `state-conflict` with reason `locked_by_legacy`.",
                        "x-plnnrly-server-assigned": true
                    },
                    "legacy_invoice_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "The id of the legacy invoice this invoice was converted from, so it can be matched with `GET /api/v2/invoices`. The legacy invoice may since have been removed.",
                        "x-plnnrly-server-assigned": true
                    },
                    "internal_notes": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "x-plnnrly-multiline": true,
                        "description": "Notes for colleagues. Never rendered on the invoice the customer receives."
                    },
                    "rows_count": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Number of line rows on this invoice."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync. It also feeds the `ETag` revision, because a sales invoice has no `version` field."
                    },
                    "deleted_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "Soft-delete timestamp, `null` while live."
                    },
                    "links": {
                        "$ref": "#/components/schemas/SalesInvoiceLinks"
                    }
                },
                "required": [
                    "id",
                    "customer_id",
                    "customer",
                    "project_id",
                    "customer_subscription_id",
                    "invoice_number",
                    "reference",
                    "description",
                    "invoice_date",
                    "due_date",
                    "status",
                    "should_email",
                    "subtotal",
                    "vat_total",
                    "total",
                    "exact_invoice_id",
                    "exact_journal",
                    "sync_status",
                    "sync_error",
                    "synced_at",
                    "sync_attempted_at",
                    "paid_at",
                    "is_legacy",
                    "legacy_invoice_id",
                    "internal_notes",
                    "rows_count",
                    "created_at",
                    "updated_at",
                    "deleted_at",
                    "links"
                ],
                "description": "A sales invoice header: what a customer is billed, when it is due, and how far it has got through the accounting sync. The money on it is the sum of its rows, which are reached through `sales-invoices/{sales_invoice}/rows`; the API never writes rows inline. Invoices that predate the current invoicing flow are marked `is_legacy` and are read-only."
            },
            "SalesInvoiceLinks": {
                "type": "object",
                "properties": {
                    "self": {
                        "type": "string",
                        "format": "uri",
                        "description": "The canonical URI of this record."
                    },
                    "rows": {
                        "type": "string",
                        "format": "uri",
                        "description": "Line rows on this invoice."
                    }
                },
                "required": [
                    "self",
                    "rows"
                ],
                "additionalProperties": false
            },
            "SalesInvoiceList": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "customer_id": {
                        "type": "integer",
                        "description": "Customer this invoice is billed to."
                    },
                    "customer": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/CustomerSummary"
                            }
                        ],
                        "description": "The customer this invoice is billed to. Expanding it with `?include=customer` additionally requires the `customers::read` permission and a token scope covering the customers prefix."
                    },
                    "project_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Project this invoice was raised for, when it was raised from one."
                    },
                    "customer_subscription_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Customer subscription this invoice was generated from, when it was generated from a renewal."
                    },
                    "invoice_number": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Invoice number, allocated by the server from the organisation's yearly sequence: B-26-00001 for an invoice raised in this flow, L-1234 for one converted from a legacy invoice. It also works in the path: `GET sales-invoices/B-26-00001` works. This is the field `?q=` searches.",
                        "x-plnnrly-server-assigned": true
                    },
                    "reference": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "The customer's own reference, typically a purchase-order number, reproduced on the invoice."
                    },
                    "description": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "One-line description of what was invoiced."
                    },
                    "invoice_date": {
                        "type": "string",
                        "format": "date",
                        "description": "Invoice date, a zone-free calendar date. Newest first is the default sort."
                    },
                    "due_date": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date",
                        "description": "Date payment is due, a zone-free calendar date. Null when no term was applied."
                    },
                    "status": {
                        "type": "string",
                        "enum": [
                            "draft",
                            "pending_approval",
                            "sent",
                            "paid",
                            "overdue",
                            "cancelled"
                        ],
                        "x-extensible-enum": true,
                        "description": "Lifecycle status. Resolved by the server on create and never writable: a caller without `sales_invoices::finalize` creates a `pending_approval` invoice, and any update by such a caller returns the invoice to `pending_approval`.",
                        "x-plnnrly-server-assigned": true
                    },
                    "should_email": {
                        "type": "boolean",
                        "description": "Whether the invoice is emailed to the customer when it syncs. The email is sent through the accounting integration, so it is forced to `false` in an organisation with no integration configured. Writing it requires `sales_invoices::finalize`."
                    },
                    "subtotal": {
                        "type": "string",
                        "format": "decimal",
                        "pattern": "^-?\\d+(\\.\\d{1,2})?$",
                        "description": "Sum of the row line totals excluding VAT, as a decimal string. Recomputed from the rows; never written directly.",
                        "x-plnnrly-server-assigned": true
                    },
                    "vat_total": {
                        "type": "string",
                        "format": "decimal",
                        "pattern": "^-?\\d+(\\.\\d{1,2})?$",
                        "description": "Sum of the row VAT amounts, as a decimal string. Recomputed from the rows; never written directly.",
                        "x-plnnrly-server-assigned": true
                    },
                    "total": {
                        "type": "string",
                        "format": "decimal",
                        "pattern": "^-?\\d+(\\.\\d{1,2})?$",
                        "description": "Subtotal plus VAT, as a decimal string. Recomputed from the rows; never written directly.",
                        "x-plnnrly-server-assigned": true
                    },
                    "exact_invoice_id": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "The accounting system's own identifier for this invoice, written by the sync. Not a local id.",
                        "x-plnnrly-server-assigned": true
                    },
                    "exact_journal": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Sales journal the invoice is booked in, resolved from the organisation's accounting settings on create. Null in an organisation with no integration configured.",
                        "x-plnnrly-server-assigned": true
                    },
                    "sync_status": {
                        "type": "string",
                        "enum": [
                            "pending",
                            "synced",
                            "failed",
                            "local"
                        ],
                        "x-extensible-enum": true,
                        "description": "How far the invoice has got through the accounting sync. Set on create: local in an organisation with no integration configured, pending otherwise.",
                        "x-plnnrly-server-assigned": true
                    },
                    "synced_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "When the invoice last synced successfully.",
                        "x-plnnrly-server-assigned": true
                    },
                    "sync_attempted_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "When the invoice was last handed to the sync, successfully or not.",
                        "x-plnnrly-server-assigned": true
                    },
                    "paid_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "When the invoice was settled. Set on create for a local invoice that is immediately definitive; otherwise written when payment is registered.",
                        "x-plnnrly-server-assigned": true
                    },
                    "is_legacy": {
                        "type": "boolean",
                        "description": "Whether this invoice was converted from a legacy invoice. Such an invoice is read-only: PATCH and DELETE answer `409` `state-conflict` with reason `locked_by_legacy`.",
                        "x-plnnrly-server-assigned": true
                    },
                    "legacy_invoice_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "The id of the legacy invoice this invoice was converted from, so it can be matched with `GET /api/v2/invoices`. The legacy invoice may since have been removed.",
                        "x-plnnrly-server-assigned": true
                    },
                    "rows_count": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Number of line rows on this invoice."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync. It also feeds the `ETag` revision, because a sales invoice has no `version` field."
                    },
                    "deleted_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "Soft-delete timestamp, `null` while live."
                    },
                    "links": {
                        "$ref": "#/components/schemas/SalesInvoiceLinks"
                    }
                },
                "required": [
                    "id",
                    "customer_id",
                    "customer",
                    "project_id",
                    "customer_subscription_id",
                    "invoice_number",
                    "reference",
                    "description",
                    "invoice_date",
                    "due_date",
                    "status",
                    "should_email",
                    "subtotal",
                    "vat_total",
                    "total",
                    "exact_invoice_id",
                    "exact_journal",
                    "sync_status",
                    "synced_at",
                    "sync_attempted_at",
                    "paid_at",
                    "is_legacy",
                    "legacy_invoice_id",
                    "rows_count",
                    "created_at",
                    "updated_at",
                    "deleted_at",
                    "links"
                ],
                "description": "A sales invoice header: what a customer is billed, when it is due, and how far it has got through the accounting sync. The money on it is the sum of its rows, which are reached through `sales-invoices/{sales_invoice}/rows`; the API never writes rows inline. Invoices that predate the current invoicing flow are marked `is_legacy` and are read-only."
            },
            "SalesInvoiceRow": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "sales_invoice_id": {
                        "type": "integer",
                        "description": "The invoice this line belongs to. Taken from the path on create and never writable afterwards: a line cannot be moved to another invoice.",
                        "x-plnnrly-server-assigned": true
                    },
                    "sales_invoice": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/SalesInvoiceSummary"
                            }
                        ],
                        "description": "The invoice this line belongs to."
                    },
                    "subscription_item_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Subscription line this invoice line was generated from, when the row came out of a subscription renewal rather than being typed by hand."
                    },
                    "budget_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Project budget this invoice line settles, when the row was generated from budget hours."
                    },
                    "exact_item_id": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Exact Online article identifier this line bills against. Empty is accepted, but a row without it fails the whole invoice's Exact sync."
                    },
                    "exact_vat_code": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Exact Online VAT code, by code rather than by id."
                    },
                    "exact_cost_center": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Exact Online cost-centre code."
                    },
                    "exact_gl_account": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Exact Online general-ledger revenue account for this line."
                    },
                    "name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "The line label, as printed on the invoice. Exact Online prints this when description is empty."
                    },
                    "description": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "x-plnnrly-multiline": true,
                        "description": "Longer line text. Exact Online prints this in preference to name."
                    },
                    "quantity": {
                        "type": "string",
                        "format": "decimal",
                        "pattern": "^-?\\d+(\\.\\d{1,2})?$",
                        "description": "Number of units billed. Fractional and signed: a budget row carries hours, and a credit line carries a negative quantity."
                    },
                    "unit_price": {
                        "type": "string",
                        "format": "decimal",
                        "pattern": "^-?\\d+(\\.\\d{1,2})?$",
                        "description": "Price per unit, excluding VAT. This is what actually bills."
                    },
                    "hourly_rate": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "decimal",
                        "pattern": "^-?\\d+(\\.\\d{1,2})?$",
                        "description": "Rate plnnrly recomputes `unit_price` from when the aggregated-price toggle is switched on a budget row. Stored exactly as sent and `null` when omitted; it feeds no server-side computation."
                    },
                    "billable_minutes": {
                        "type": "integer",
                        "description": "Minutes of budget time this line settles. Zero on a hand-typed row."
                    },
                    "is_aggregated_price": {
                        "type": "boolean",
                        "description": "True: one line of quantity 1 priced at hours times rate. False: quantity carries the hours and `unit_price` the hourly rate."
                    },
                    "vat_rate": {
                        "type": "integer",
                        "description": "VAT percentage as a whole number: 21 means 21%."
                    },
                    "vat_amount": {
                        "type": "string",
                        "format": "decimal",
                        "pattern": "^-?\\d+(\\.\\d{1,2})?$",
                        "description": "VAT on this line: quantity times `unit_price` times `vat_rate`, rounded to two decimals. Recomputed by the server whenever one of those three changes.",
                        "x-plnnrly-server-assigned": true
                    },
                    "total_amount": {
                        "type": "string",
                        "format": "decimal",
                        "pattern": "^-?\\d+(\\.\\d{1,2})?$",
                        "description": "Line total excluding VAT (quantity times `unit_price`), which is the convention the invoice form writes. Recomputed by the server whenever quantity or `unit_price` changes.",
                        "x-plnnrly-server-assigned": true
                    },
                    "sort_order": {
                        "type": "integer",
                        "description": "Position of the line within its invoice, and the default sort key. Assigned on create; there is no way to reorder lines through the API.",
                        "x-plnnrly-server-assigned": true
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "deleted_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "Soft-delete timestamp, `null` while live."
                    },
                    "links": {
                        "$ref": "#/components/schemas/SalesInvoiceRowLinks"
                    }
                },
                "required": [
                    "id",
                    "sales_invoice_id",
                    "sales_invoice",
                    "subscription_item_id",
                    "budget_id",
                    "exact_item_id",
                    "exact_vat_code",
                    "exact_cost_center",
                    "exact_gl_account",
                    "name",
                    "description",
                    "quantity",
                    "unit_price",
                    "hourly_rate",
                    "billable_minutes",
                    "is_aggregated_price",
                    "vat_rate",
                    "vat_amount",
                    "total_amount",
                    "sort_order",
                    "created_at",
                    "updated_at",
                    "deleted_at",
                    "links"
                ],
                "description": "One line on a sales invoice: a label, a quantity, a unit price and a VAT percentage. An invoice's subtotal, VAT total and total are computed from nothing but its rows, and a row that settles budget hours also records the minutes it covers."
            },
            "SalesInvoiceRowLinks": {
                "type": "object",
                "properties": {
                    "self": {
                        "type": "string",
                        "format": "uri",
                        "description": "The canonical URI of this record."
                    }
                },
                "required": [
                    "self"
                ],
                "additionalProperties": false
            },
            "SalesInvoiceRowList": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "sales_invoice_id": {
                        "type": "integer",
                        "description": "The invoice this line belongs to. Taken from the path on create and never writable afterwards: a line cannot be moved to another invoice.",
                        "x-plnnrly-server-assigned": true
                    },
                    "sales_invoice": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/SalesInvoiceSummary"
                            }
                        ],
                        "description": "The invoice this line belongs to."
                    },
                    "subscription_item_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Subscription line this invoice line was generated from, when the row came out of a subscription renewal rather than being typed by hand."
                    },
                    "budget_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Project budget this invoice line settles, when the row was generated from budget hours."
                    },
                    "exact_item_id": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Exact Online article identifier this line bills against. Empty is accepted, but a row without it fails the whole invoice's Exact sync."
                    },
                    "exact_vat_code": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Exact Online VAT code, by code rather than by id."
                    },
                    "exact_cost_center": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Exact Online cost-centre code."
                    },
                    "exact_gl_account": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Exact Online general-ledger revenue account for this line."
                    },
                    "name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "The line label, as printed on the invoice. Exact Online prints this when description is empty."
                    },
                    "description": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "x-plnnrly-multiline": true,
                        "description": "Longer line text. Exact Online prints this in preference to name."
                    },
                    "quantity": {
                        "type": "string",
                        "format": "decimal",
                        "pattern": "^-?\\d+(\\.\\d{1,2})?$",
                        "description": "Number of units billed. Fractional and signed: a budget row carries hours, and a credit line carries a negative quantity."
                    },
                    "unit_price": {
                        "type": "string",
                        "format": "decimal",
                        "pattern": "^-?\\d+(\\.\\d{1,2})?$",
                        "description": "Price per unit, excluding VAT. This is what actually bills."
                    },
                    "hourly_rate": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "decimal",
                        "pattern": "^-?\\d+(\\.\\d{1,2})?$",
                        "description": "Rate plnnrly recomputes `unit_price` from when the aggregated-price toggle is switched on a budget row. Stored exactly as sent and `null` when omitted; it feeds no server-side computation."
                    },
                    "billable_minutes": {
                        "type": "integer",
                        "description": "Minutes of budget time this line settles. Zero on a hand-typed row."
                    },
                    "is_aggregated_price": {
                        "type": "boolean",
                        "description": "True: one line of quantity 1 priced at hours times rate. False: quantity carries the hours and `unit_price` the hourly rate."
                    },
                    "vat_rate": {
                        "type": "integer",
                        "description": "VAT percentage as a whole number: 21 means 21%."
                    },
                    "vat_amount": {
                        "type": "string",
                        "format": "decimal",
                        "pattern": "^-?\\d+(\\.\\d{1,2})?$",
                        "description": "VAT on this line: quantity times `unit_price` times `vat_rate`, rounded to two decimals. Recomputed by the server whenever one of those three changes.",
                        "x-plnnrly-server-assigned": true
                    },
                    "total_amount": {
                        "type": "string",
                        "format": "decimal",
                        "pattern": "^-?\\d+(\\.\\d{1,2})?$",
                        "description": "Line total excluding VAT (quantity times `unit_price`), which is the convention the invoice form writes. Recomputed by the server whenever quantity or `unit_price` changes.",
                        "x-plnnrly-server-assigned": true
                    },
                    "sort_order": {
                        "type": "integer",
                        "description": "Position of the line within its invoice, and the default sort key. Assigned on create; there is no way to reorder lines through the API.",
                        "x-plnnrly-server-assigned": true
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "deleted_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "Soft-delete timestamp, `null` while live."
                    },
                    "links": {
                        "$ref": "#/components/schemas/SalesInvoiceRowLinks"
                    }
                },
                "required": [
                    "id",
                    "sales_invoice_id",
                    "sales_invoice",
                    "subscription_item_id",
                    "budget_id",
                    "exact_item_id",
                    "exact_vat_code",
                    "exact_cost_center",
                    "exact_gl_account",
                    "name",
                    "description",
                    "quantity",
                    "unit_price",
                    "hourly_rate",
                    "billable_minutes",
                    "is_aggregated_price",
                    "vat_rate",
                    "vat_amount",
                    "total_amount",
                    "sort_order",
                    "created_at",
                    "updated_at",
                    "deleted_at",
                    "links"
                ],
                "description": "One line on a sales invoice: a label, a quantity, a unit price and a VAT percentage. An invoice's subtotal, VAT total and total are computed from nothing but its rows, and a row that settles budget hours also records the minutes it covers."
            },
            "SalesInvoiceRowSummary": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "The line label, as printed on the invoice."
                    },
                    "total_amount": {
                        "type": "string",
                        "format": "decimal",
                        "pattern": "^-?\\d+(\\.\\d{1,2})?$",
                        "description": "Line total, excluding VAT."
                    }
                },
                "required": [
                    "id",
                    "name",
                    "total_amount"
                ],
                "description": "A sales-invoice line, as referenced from another resource."
            },
            "SalesInvoiceSummary": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "invoice_number": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Invoice number, e.g. B-26-00001."
                    },
                    "invoice_date": {
                        "type": "string",
                        "format": "date",
                        "description": "Invoice date, a zone-free calendar date."
                    }
                },
                "required": [
                    "id",
                    "invoice_number",
                    "invoice_date"
                ],
                "description": "A sales invoice, as referenced from another resource."
            },
            "SalesInvoicesActionsRestoreMeta": {
                "type": "object",
                "properties": {
                    "warnings": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "code": {
                                    "type": "string",
                                    "enum": [
                                        "already_in_target_state"
                                    ],
                                    "x-extensible-enum": true,
                                    "description": "Machine-readable warning code. Switch on this, never on detail."
                                },
                                "detail": {
                                    "type": "string",
                                    "description": "Human-readable explanation."
                                },
                                "pointer": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "description": "RFC 6901 pointer into the request body, where the warning is about a body field."
                                },
                                "parameter": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "description": "The query-parameter name, brackets included, where the warning is about a parameter."
                                },
                                "sunset": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "format": "date",
                                    "description": "Date the deprecated thing stops working."
                                },
                                "replacement": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "description": "What to use instead."
                                }
                            },
                            "required": [
                                "code",
                                "detail"
                            ],
                            "description": "An advisory about this request. It never changes the status code or `data`."
                        },
                        "description": "Non-fatal notes about this request. Switch on `code`, never on `detail`."
                    }
                },
                "additionalProperties": false
            },
            "SalesInvoicesIndexMeta": {
                "type": "object",
                "properties": {
                    "page": {
                        "$ref": "#/components/schemas/Page"
                    }
                },
                "additionalProperties": false,
                "required": [
                    "page"
                ]
            },
            "SalesInvoicesRowsIndexMeta": {
                "type": "object",
                "properties": {
                    "page": {
                        "$ref": "#/components/schemas/Page"
                    }
                },
                "additionalProperties": false,
                "required": [
                    "page"
                ]
            },
            "SalesInvoicesShowMeta": {
                "type": "object",
                "properties": {
                    "actions": {
                        "type": "array",
                        "items": {
                            "type": "string",
                            "enum": [
                                "restore"
                            ]
                        },
                        "description": "The actions the caller may currently invoke on this record."
                    }
                },
                "additionalProperties": false
            },
            "Sprint": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "project_id": {
                        "type": "integer",
                        "description": "The project this sprint belongs to. A sprint cannot be moved between projects, so this is accepted on create only."
                    },
                    "name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Sprint name as shown in plnnrly, e.g. `Sprint 14`."
                    },
                    "description": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "x-plnnrly-multiline": true,
                        "description": "Free-text sprint goal. Plain text, not markup."
                    },
                    "start_date": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date",
                        "description": "Planned first day of the sprint. Independent of `started_at`: planning a date does not start the sprint."
                    },
                    "end_date": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date",
                        "description": "Planned last day of the sprint. Independent of `completed_at`."
                    },
                    "started_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "When the sprint was actually started. Written only by POST `sprints/{sprint}/actions/start`; `null` on an upcoming sprint.",
                        "x-plnnrly-server-assigned": true
                    },
                    "completed_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "When the sprint was actually completed. Written only by POST `sprints/{sprint}/actions/complete`; `null` until then. Non-`null` is what makes the derived state `completed`.",
                        "x-plnnrly-server-assigned": true
                    },
                    "is_active": {
                        "type": "boolean",
                        "description": "Whether this is the project's running sprint. Set by actions/start and cleared by actions/complete, never by PATCH. At most one sprint per project is active at a time.",
                        "x-plnnrly-server-assigned": true
                    },
                    "order": {
                        "type": "integer",
                        "description": "Display position within the project. Assigned on create as the last position in that project's sprint order, changed only by reordering in plnnrly, and not writable.",
                        "x-plnnrly-server-assigned": true
                    },
                    "work_items_count": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Number of work items currently on this sprint, resolved and unresolved alike."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "project": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/ProjectSummary"
                            }
                        ],
                        "description": "The project this sprint belongs to. Expanding it additionally requires the projects prefix's read grant and a token scope covering it."
                    },
                    "links": {
                        "$ref": "#/components/schemas/SprintLinks"
                    }
                },
                "required": [
                    "id",
                    "project_id",
                    "name",
                    "description",
                    "start_date",
                    "end_date",
                    "started_at",
                    "completed_at",
                    "is_active",
                    "order",
                    "work_items_count",
                    "created_at",
                    "updated_at",
                    "project",
                    "links"
                ],
                "description": "A sprint: a time-boxed slice of a project's backlog. A sprint has three states and they are derived, not stored (`completed_at` set means completed, otherwise `is_active` `true` means active, otherwise upcoming), so there is no `state` field to read and no `filter[state]` to send. Starting and completing a sprint are not field writes: they live at POST `sprints/{sprint}/actions/start` and .../actions/complete, because either can fail for a state reason rather than an invalid-value reason."
            },
            "SprintLinks": {
                "type": "object",
                "properties": {
                    "self": {
                        "type": "string",
                        "format": "uri",
                        "description": "The canonical URI of this record."
                    },
                    "work_items": {
                        "type": "string",
                        "format": "uri",
                        "description": "Work items on this sprint."
                    }
                },
                "required": [
                    "self",
                    "work_items"
                ],
                "additionalProperties": false
            },
            "SprintList": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "project_id": {
                        "type": "integer",
                        "description": "The project this sprint belongs to. A sprint cannot be moved between projects, so this is accepted on create only."
                    },
                    "name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Sprint name as shown in plnnrly, e.g. `Sprint 14`."
                    },
                    "description": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "x-plnnrly-multiline": true,
                        "description": "Free-text sprint goal. Plain text, not markup."
                    },
                    "start_date": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date",
                        "description": "Planned first day of the sprint. Independent of `started_at`: planning a date does not start the sprint."
                    },
                    "end_date": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date",
                        "description": "Planned last day of the sprint. Independent of `completed_at`."
                    },
                    "started_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "When the sprint was actually started. Written only by POST `sprints/{sprint}/actions/start`; `null` on an upcoming sprint.",
                        "x-plnnrly-server-assigned": true
                    },
                    "completed_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "When the sprint was actually completed. Written only by POST `sprints/{sprint}/actions/complete`; `null` until then. Non-`null` is what makes the derived state `completed`.",
                        "x-plnnrly-server-assigned": true
                    },
                    "is_active": {
                        "type": "boolean",
                        "description": "Whether this is the project's running sprint. Set by actions/start and cleared by actions/complete, never by PATCH. At most one sprint per project is active at a time.",
                        "x-plnnrly-server-assigned": true
                    },
                    "order": {
                        "type": "integer",
                        "description": "Display position within the project. Assigned on create as the last position in that project's sprint order, changed only by reordering in plnnrly, and not writable.",
                        "x-plnnrly-server-assigned": true
                    },
                    "work_items_count": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Number of work items currently on this sprint, resolved and unresolved alike."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "project": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/ProjectSummary"
                            }
                        ],
                        "description": "The project this sprint belongs to. Expanding it additionally requires the projects prefix's read grant and a token scope covering it."
                    },
                    "links": {
                        "$ref": "#/components/schemas/SprintLinks"
                    }
                },
                "required": [
                    "id",
                    "project_id",
                    "name",
                    "description",
                    "start_date",
                    "end_date",
                    "started_at",
                    "completed_at",
                    "is_active",
                    "order",
                    "work_items_count",
                    "created_at",
                    "updated_at",
                    "project",
                    "links"
                ],
                "description": "A sprint: a time-boxed slice of a project's backlog. A sprint has three states and they are derived, not stored (`completed_at` set means completed, otherwise `is_active` `true` means active, otherwise upcoming), so there is no `state` field to read and no `filter[state]` to send. Starting and completing a sprint are not field writes: they live at POST `sprints/{sprint}/actions/start` and .../actions/complete, because either can fail for a state reason rather than an invalid-value reason."
            },
            "SprintSummary": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Sprint name as shown in the UI."
                    },
                    "is_active": {
                        "type": "boolean",
                        "description": "Whether this is the project's running sprint."
                    }
                },
                "required": [
                    "id",
                    "name",
                    "is_active"
                ],
                "description": "A sprint, as referenced from another resource."
            },
            "SprintsIndexMeta": {
                "type": "object",
                "properties": {
                    "page": {
                        "$ref": "#/components/schemas/Page"
                    }
                },
                "additionalProperties": false,
                "required": [
                    "page"
                ]
            },
            "SprintsShowMeta": {
                "type": "object",
                "properties": {
                    "actions": {
                        "type": "array",
                        "items": {
                            "type": "string",
                            "enum": [
                                "complete",
                                "start"
                            ]
                        },
                        "description": "The actions the caller may currently invoke on this record."
                    }
                },
                "additionalProperties": false
            },
            "StateConflictProblem": {
                "type": "object",
                "properties": {
                    "type": {
                        "type": "string",
                        "format": "uri",
                        "const": "https://developers.plnnrly.com/api/v2/problems/state-conflict",
                        "description": "The problem type. This is the only member a client switches on."
                    },
                    "title": {
                        "type": "string",
                        "description": "Short, stable per type, English, never interpolated. Never parse it."
                    },
                    "status": {
                        "type": "integer",
                        "const": 409,
                        "description": "The HTTP status, repeated in the body."
                    },
                    "detail": {
                        "type": "string",
                        "description": "Human-readable explanation. May quote values from your request; never shows internal error output."
                    },
                    "instance": {
                        "type": "string",
                        "description": "The request path, with no query string."
                    },
                    "request_id": {
                        "type": "string",
                        "description": "The id also in the X-Request-Id header. Quote it when you report a problem to plnnrly support."
                    },
                    "conflict": {
                        "type": "object",
                        "properties": {
                            "reason": {
                                "type": "string",
                                "enum": [
                                    "illegal_transition",
                                    "already_started",
                                    "already_completed",
                                    "not_started",
                                    "has_logged_time",
                                    "has_children",
                                    "unconfigured",
                                    "locked_by_review",
                                    "locked_by_invoice",
                                    "reason_reviewed",
                                    "locked_by_legacy",
                                    "locked_by_sync",
                                    "too_many_tokens",
                                    "too_many_pending_uploads",
                                    "upload_not_received",
                                    "upload_expired",
                                    "upload_revoked",
                                    "upload_already_registered",
                                    "upload_registration_in_progress",
                                    "upload_object_missing",
                                    "upload_object_changed",
                                    "upload_token_mismatch"
                                ],
                                "x-extensible-enum": true,
                                "description": "Why the record's current state forbids the operation."
                            }
                        },
                        "required": [
                            "reason"
                        ],
                        "additionalProperties": true,
                        "description": "Machine-readable context. `reason` is always present; the other keys depend on it."
                    }
                },
                "required": [
                    "type",
                    "title",
                    "status",
                    "detail",
                    "instance",
                    "request_id"
                ],
                "description": "State conflict"
            },
            "SubscriptionItem": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "customer_subscription_id": {
                        "type": "integer",
                        "description": "The subscription this line belongs to. Taken from the path on create and never writable afterwards: a line cannot be moved to another subscription.",
                        "x-plnnrly-server-assigned": true
                    },
                    "customer_subscription": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/CustomerSubscriptionSummary"
                            }
                        ],
                        "description": "The subscription this line belongs to."
                    },
                    "subscribable_type": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "enum": [
                            "subscription_product",
                            null
                        ],
                        "x-extensible-enum": true,
                        "description": "The kind of record `subscribable` points at; it says which branch of `subscribable` applies."
                    },
                    "subscribable_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "The id of the record `subscribable` points at."
                    },
                    "subscribable": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/SubscriptionProductSummary"
                            },
                            {
                                "type": "null"
                            }
                        ],
                        "x-plnnrly-discriminator": {
                            "propertyName": "subscribable_type",
                            "mapping": {
                                "subscription_product": "#/components/schemas/SubscriptionProductSummary"
                            }
                        },
                        "x-plnnrly-morph-aliases": [
                            "subscription_product"
                        ],
                        "description": "What this line sells. Rendered as {\"id\": N} until named in `?include=subscribable`, which additionally requires the read permission and token scope of the `subscription_products` prefix. Lines that bill a hosting domain or a server carry a `subscribable_type` this API publishes no shape for, and keep the {\"id\": N} form however they are requested; identify those from `subscribable_type` and `subscribable_id` yourself. Null on both halves is a free-text line priced by hand."
                    },
                    "description": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "x-plnnrly-multiline": true,
                        "description": "The line text, as printed on the invoice a renewal generates. May contain placeholders such as {{$domain}}; the API publishes the raw stored text and those are resolved only at invoice time."
                    },
                    "quantity": {
                        "type": "integer",
                        "description": "Number of units billed per period."
                    },
                    "purchase_price": {
                        "type": "string",
                        "format": "decimal",
                        "pattern": "^\\d+(\\.\\d{1,2})?$",
                        "x-plnnrly-minimum": 0,
                        "description": "Cost price per unit, for margin bookkeeping only. It feeds neither generated amount below and appears on no invoice."
                    },
                    "sales_price": {
                        "type": "string",
                        "format": "decimal",
                        "pattern": "^\\d+(\\.\\d{1,2})?$",
                        "x-plnnrly-minimum": 0,
                        "description": "Sales price per unit, excluding VAT. This is what actually bills: a renewal copies it into the generated invoice row's unit price."
                    },
                    "vat_rate": {
                        "type": "integer",
                        "description": "VAT percentage as a whole number: 21 means 21%."
                    },
                    "vat_amount": {
                        "type": "string",
                        "format": "decimal",
                        "pattern": "^-?\\d+(\\.\\d{1,2})?$",
                        "description": "VAT on this line, computed by the server: quantity x `sales_price` x `vat_rate` / 100. Read-only.",
                        "x-plnnrly-server-assigned": true
                    },
                    "total_amount": {
                        "type": "string",
                        "format": "decimal",
                        "pattern": "^-?\\d+(\\.\\d{1,2})?$",
                        "description": "Line total including VAT (quantity x `sales_price` plus the VAT above), computed by the server. Note this is the opposite convention from `SalesInvoiceRow.total_amount`, which excludes VAT: adding VAT to this value double-counts it. Read-only.",
                        "x-plnnrly-server-assigned": true
                    },
                    "sort_order": {
                        "type": "integer",
                        "description": "Position of the line within its subscription, and the default sort key. Assigned on create; there is no way to reorder lines through the API. Only the ordering is meaningful: values are not unique across subscriptions.",
                        "x-plnnrly-server-assigned": true
                    },
                    "exact_item_id": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Exact Online article identifier this line bills against. Optional: a renewal falls back to the subscription product's own Exact values and then to the organisation's Exact defaults, so leaving it empty does not break the sync."
                    },
                    "exact_vat_code": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Exact Online VAT code, by code rather than by id."
                    },
                    "exact_gl_account": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Exact Online general-ledger revenue account for this line."
                    },
                    "exact_cost_center": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Exact Online cost-centre code."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync. This is also the `ETag` revision: a subscription line has no `version` field."
                    },
                    "deleted_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "Soft-delete timestamp, `null` while live."
                    },
                    "links": {
                        "$ref": "#/components/schemas/SubscriptionItemLinks"
                    }
                },
                "required": [
                    "id",
                    "customer_subscription_id",
                    "customer_subscription",
                    "subscribable_type",
                    "subscribable_id",
                    "subscribable",
                    "description",
                    "quantity",
                    "purchase_price",
                    "sales_price",
                    "vat_rate",
                    "vat_amount",
                    "total_amount",
                    "sort_order",
                    "exact_item_id",
                    "exact_vat_code",
                    "exact_gl_account",
                    "exact_cost_center",
                    "created_at",
                    "updated_at",
                    "deleted_at",
                    "links"
                ],
                "description": "One line on a customer subscription: what is being sold, how many, at what purchase and sales price, and at what VAT percentage. These lines are what a renewal turns into sales-invoice rows, and a subscription with no lines cannot be invoiced.",
                "x-plnnrly-omitted": {
                    "resource_id": "The other half of that pair, withheld with it.",
                    "resource_type": "The hosting site or server a line is sold for has no published shape in this API, so it is not exposed."
                }
            },
            "SubscriptionItemLinks": {
                "type": "object",
                "properties": {
                    "self": {
                        "type": "string",
                        "format": "uri",
                        "description": "The canonical URI of this record."
                    }
                },
                "required": [
                    "self"
                ],
                "additionalProperties": false
            },
            "SubscriptionItemList": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "customer_subscription_id": {
                        "type": "integer",
                        "description": "The subscription this line belongs to. Taken from the path on create and never writable afterwards: a line cannot be moved to another subscription.",
                        "x-plnnrly-server-assigned": true
                    },
                    "customer_subscription": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/CustomerSubscriptionSummary"
                            }
                        ],
                        "description": "The subscription this line belongs to."
                    },
                    "subscribable_type": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "enum": [
                            "subscription_product",
                            null
                        ],
                        "x-extensible-enum": true,
                        "description": "The kind of record `subscribable` points at; it says which branch of `subscribable` applies."
                    },
                    "subscribable_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "The id of the record `subscribable` points at."
                    },
                    "subscribable": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/SubscriptionProductSummary"
                            },
                            {
                                "type": "null"
                            }
                        ],
                        "x-plnnrly-discriminator": {
                            "propertyName": "subscribable_type",
                            "mapping": {
                                "subscription_product": "#/components/schemas/SubscriptionProductSummary"
                            }
                        },
                        "x-plnnrly-morph-aliases": [
                            "subscription_product"
                        ],
                        "description": "What this line sells. Rendered as {\"id\": N} until named in `?include=subscribable`, which additionally requires the read permission and token scope of the `subscription_products` prefix. Lines that bill a hosting domain or a server carry a `subscribable_type` this API publishes no shape for, and keep the {\"id\": N} form however they are requested; identify those from `subscribable_type` and `subscribable_id` yourself. Null on both halves is a free-text line priced by hand."
                    },
                    "description": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "x-plnnrly-multiline": true,
                        "description": "The line text, as printed on the invoice a renewal generates. May contain placeholders such as {{$domain}}; the API publishes the raw stored text and those are resolved only at invoice time."
                    },
                    "quantity": {
                        "type": "integer",
                        "description": "Number of units billed per period."
                    },
                    "purchase_price": {
                        "type": "string",
                        "format": "decimal",
                        "pattern": "^\\d+(\\.\\d{1,2})?$",
                        "x-plnnrly-minimum": 0,
                        "description": "Cost price per unit, for margin bookkeeping only. It feeds neither generated amount below and appears on no invoice."
                    },
                    "sales_price": {
                        "type": "string",
                        "format": "decimal",
                        "pattern": "^\\d+(\\.\\d{1,2})?$",
                        "x-plnnrly-minimum": 0,
                        "description": "Sales price per unit, excluding VAT. This is what actually bills: a renewal copies it into the generated invoice row's unit price."
                    },
                    "vat_rate": {
                        "type": "integer",
                        "description": "VAT percentage as a whole number: 21 means 21%."
                    },
                    "vat_amount": {
                        "type": "string",
                        "format": "decimal",
                        "pattern": "^-?\\d+(\\.\\d{1,2})?$",
                        "description": "VAT on this line, computed by the server: quantity x `sales_price` x `vat_rate` / 100. Read-only.",
                        "x-plnnrly-server-assigned": true
                    },
                    "total_amount": {
                        "type": "string",
                        "format": "decimal",
                        "pattern": "^-?\\d+(\\.\\d{1,2})?$",
                        "description": "Line total including VAT (quantity x `sales_price` plus the VAT above), computed by the server. Note this is the opposite convention from `SalesInvoiceRow.total_amount`, which excludes VAT: adding VAT to this value double-counts it. Read-only.",
                        "x-plnnrly-server-assigned": true
                    },
                    "sort_order": {
                        "type": "integer",
                        "description": "Position of the line within its subscription, and the default sort key. Assigned on create; there is no way to reorder lines through the API. Only the ordering is meaningful: values are not unique across subscriptions.",
                        "x-plnnrly-server-assigned": true
                    },
                    "exact_item_id": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Exact Online article identifier this line bills against. Optional: a renewal falls back to the subscription product's own Exact values and then to the organisation's Exact defaults, so leaving it empty does not break the sync."
                    },
                    "exact_vat_code": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Exact Online VAT code, by code rather than by id."
                    },
                    "exact_gl_account": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Exact Online general-ledger revenue account for this line."
                    },
                    "exact_cost_center": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Exact Online cost-centre code."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync. This is also the `ETag` revision: a subscription line has no `version` field."
                    },
                    "deleted_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "Soft-delete timestamp, `null` while live."
                    },
                    "links": {
                        "$ref": "#/components/schemas/SubscriptionItemLinks"
                    }
                },
                "required": [
                    "id",
                    "customer_subscription_id",
                    "customer_subscription",
                    "subscribable_type",
                    "subscribable_id",
                    "subscribable",
                    "description",
                    "quantity",
                    "purchase_price",
                    "sales_price",
                    "vat_rate",
                    "vat_amount",
                    "total_amount",
                    "sort_order",
                    "exact_item_id",
                    "exact_vat_code",
                    "exact_gl_account",
                    "exact_cost_center",
                    "created_at",
                    "updated_at",
                    "deleted_at",
                    "links"
                ],
                "description": "One line on a customer subscription: what is being sold, how many, at what purchase and sales price, and at what VAT percentage. These lines are what a renewal turns into sales-invoice rows, and a subscription with no lines cannot be invoiced."
            },
            "SubscriptionItemSummary": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "description": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "x-plnnrly-multiline": true,
                        "description": "The line text, as stored, with placeholders unresolved."
                    }
                },
                "required": [
                    "id",
                    "description"
                ],
                "description": "A subscription line, as referenced from another resource."
            },
            "SubscriptionProduct": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Product name as shown in the UI and on the subscription line."
                    },
                    "description": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "x-plnnrly-multiline": true,
                        "description": "Invoice-line template for subscription lines built from this product. Supports the placeholder tokens {{$domain}}, {{$site}}, {{$server}}, {{$period}}, {{$product}} and {{$customer}}, which are filled in when the line is invoiced; the template itself is kept verbatim."
                    },
                    "purchase_price": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "decimal",
                        "pattern": "^-?\\d+(\\.\\d{1,2})?$",
                        "description": "What the product costs us, as a decimal string. Null when unknown."
                    },
                    "sales_price": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "decimal",
                        "pattern": "^-?\\d+(\\.\\d{1,2})?$",
                        "description": "What the customer is billed per interval, as a decimal string. Null when the line carries its own price."
                    },
                    "price_interval": {
                        "type": "string",
                        "enum": [
                            "yearly",
                            "monthly",
                            "one-time"
                        ],
                        "x-extensible-enum": true,
                        "description": "Interval the sales price is charged per."
                    },
                    "is_hosting": {
                        "type": "boolean",
                        "description": "Whether this is a hosting product: one sold against a concrete site or server, which is what lets a subscription line name the infrastructure it covers."
                    },
                    "vat_rate": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "VAT rate as a whole percentage: 21 means 21%, not 0.21. Null when the rate is decided at invoicing time."
                    },
                    "exact_item_id": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Exact Online article this product maps to, by Exact's own external identifier (`exact_items.exact_id`), not by a local id."
                    },
                    "exact_vat_code": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Exact Online VAT code, by code (`exact_vat_codes.code`), not by id."
                    },
                    "exact_cost_center": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Exact Online cost centre, by code (`exact_cost_centers.code`), not by id."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "deleted_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "Soft-delete timestamp, `null` while live."
                    },
                    "links": {
                        "$ref": "#/components/schemas/SubscriptionProductLinks"
                    }
                },
                "required": [
                    "id",
                    "name",
                    "description",
                    "purchase_price",
                    "sales_price",
                    "price_interval",
                    "is_hosting",
                    "vat_rate",
                    "exact_item_id",
                    "exact_vat_code",
                    "exact_cost_center",
                    "created_at",
                    "updated_at",
                    "deleted_at",
                    "links"
                ],
                "description": "A sellable subscription product: the organisation's hand-maintained catalogue of what a customer subscription or a subscription type can bill for. Subscription lines point at a product and inherit its price, interval and VAT rate."
            },
            "SubscriptionProductLinks": {
                "type": "object",
                "properties": {
                    "self": {
                        "type": "string",
                        "format": "uri",
                        "description": "The canonical URI of this record."
                    }
                },
                "required": [
                    "self"
                ],
                "additionalProperties": false
            },
            "SubscriptionProductList": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Product name as shown in the UI and on the subscription line."
                    },
                    "description": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "x-plnnrly-multiline": true,
                        "description": "Invoice-line template for subscription lines built from this product. Supports the placeholder tokens {{$domain}}, {{$site}}, {{$server}}, {{$period}}, {{$product}} and {{$customer}}, which are filled in when the line is invoiced; the template itself is kept verbatim."
                    },
                    "purchase_price": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "decimal",
                        "pattern": "^-?\\d+(\\.\\d{1,2})?$",
                        "description": "What the product costs us, as a decimal string. Null when unknown."
                    },
                    "sales_price": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "decimal",
                        "pattern": "^-?\\d+(\\.\\d{1,2})?$",
                        "description": "What the customer is billed per interval, as a decimal string. Null when the line carries its own price."
                    },
                    "price_interval": {
                        "type": "string",
                        "enum": [
                            "yearly",
                            "monthly",
                            "one-time"
                        ],
                        "x-extensible-enum": true,
                        "description": "Interval the sales price is charged per."
                    },
                    "is_hosting": {
                        "type": "boolean",
                        "description": "Whether this is a hosting product: one sold against a concrete site or server, which is what lets a subscription line name the infrastructure it covers."
                    },
                    "vat_rate": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "VAT rate as a whole percentage: 21 means 21%, not 0.21. Null when the rate is decided at invoicing time."
                    },
                    "exact_item_id": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Exact Online article this product maps to, by Exact's own external identifier (`exact_items.exact_id`), not by a local id."
                    },
                    "exact_vat_code": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Exact Online VAT code, by code (`exact_vat_codes.code`), not by id."
                    },
                    "exact_cost_center": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Exact Online cost centre, by code (`exact_cost_centers.code`), not by id."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "deleted_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "Soft-delete timestamp, `null` while live."
                    },
                    "links": {
                        "$ref": "#/components/schemas/SubscriptionProductLinks"
                    }
                },
                "required": [
                    "id",
                    "name",
                    "description",
                    "purchase_price",
                    "sales_price",
                    "price_interval",
                    "is_hosting",
                    "vat_rate",
                    "exact_item_id",
                    "exact_vat_code",
                    "exact_cost_center",
                    "created_at",
                    "updated_at",
                    "deleted_at",
                    "links"
                ],
                "description": "A sellable subscription product: the organisation's hand-maintained catalogue of what a customer subscription or a subscription type can bill for. Subscription lines point at a product and inherit its price, interval and VAT rate."
            },
            "SubscriptionProductSummary": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Product name."
                    }
                },
                "required": [
                    "id",
                    "name"
                ],
                "description": "A subscription product, as referenced from another resource."
            },
            "SubscriptionProductsActionsRestoreMeta": {
                "type": "object",
                "properties": {
                    "warnings": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "code": {
                                    "type": "string",
                                    "enum": [
                                        "already_in_target_state"
                                    ],
                                    "x-extensible-enum": true,
                                    "description": "Machine-readable warning code. Switch on this, never on detail."
                                },
                                "detail": {
                                    "type": "string",
                                    "description": "Human-readable explanation."
                                },
                                "pointer": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "description": "RFC 6901 pointer into the request body, where the warning is about a body field."
                                },
                                "parameter": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "description": "The query-parameter name, brackets included, where the warning is about a parameter."
                                },
                                "sunset": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "format": "date",
                                    "description": "Date the deprecated thing stops working."
                                },
                                "replacement": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "description": "What to use instead."
                                }
                            },
                            "required": [
                                "code",
                                "detail"
                            ],
                            "description": "An advisory about this request. It never changes the status code or `data`."
                        },
                        "description": "Non-fatal notes about this request. Switch on `code`, never on `detail`."
                    }
                },
                "additionalProperties": false
            },
            "SubscriptionProductsIndexMeta": {
                "type": "object",
                "properties": {
                    "page": {
                        "$ref": "#/components/schemas/Page"
                    }
                },
                "additionalProperties": false,
                "required": [
                    "page"
                ]
            },
            "SubscriptionProductsShowMeta": {
                "type": "object",
                "properties": {
                    "actions": {
                        "type": "array",
                        "items": {
                            "type": "string",
                            "enum": [
                                "restore"
                            ]
                        },
                        "description": "The actions the caller may currently invoke on this record."
                    }
                },
                "additionalProperties": false
            },
            "SubscriptionType": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Subscription type name. It is also the name every customer subscription of this type is displayed under, so renaming a type renames what its existing subscriptions read as. Names are not unique."
                    },
                    "description": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "x-plnnrly-multiline": true,
                        "description": "What subscriptions of this type cover, as free text."
                    },
                    "default_billing_interval": {
                        "type": "string",
                        "enum": [
                            "yearly",
                            "monthly",
                            "one-time"
                        ],
                        "x-extensible-enum": true,
                        "description": "Interval a new subscription of this type is billed at by default. Omitting it on create yields yearly, the stored default, not the monthly that plnnrly's form pre-selects, which is a form convenience the API deliberately does not reproduce."
                    },
                    "auto_renew": {
                        "type": "boolean",
                        "description": "Whether subscriptions created from this type renew automatically by default. The value is stored exactly as sent; nothing forces it to `false`."
                    },
                    "active": {
                        "type": "boolean",
                        "description": "Whether the type is offered in the catalogue. It is a catalogue flag only: nothing in the application filters on it, so deactivating a type does not stop existing subscriptions of that type from being renewed or invoiced."
                    },
                    "sort_order": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Position in the catalogue, assigned by the server on create as one past the highest existing position. It is the default sort key, and there is no way to reorder types through the API: dragging types into order in plnnrly has no API equivalent in v2.0.",
                        "x-plnnrly-server-assigned": true
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync. It also feeds the `ETag` revision, because a subscription type has no `version` field."
                    },
                    "deleted_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "Soft-delete timestamp, `null` while live."
                    },
                    "items_count": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Number of template lines on this subscription type."
                    },
                    "links": {
                        "$ref": "#/components/schemas/SubscriptionTypeLinks"
                    }
                },
                "required": [
                    "id",
                    "name",
                    "description",
                    "default_billing_interval",
                    "auto_renew",
                    "active",
                    "sort_order",
                    "created_at",
                    "updated_at",
                    "deleted_at",
                    "items_count",
                    "links"
                ],
                "description": "A subscription type: the organisation's hand-maintained template a customer subscription is instanced from, carrying the default billing interval and renewal behaviour new subscriptions start with. Its template lines live on the items sub-resource, and the type's name is the name plnnrly shows for every subscription of that type."
            },
            "SubscriptionTypeItem": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "subscription_type_id": {
                        "type": "integer",
                        "description": "The subscription type this template line belongs to. Taken from the path on create and never writable afterwards: a line cannot be moved to another type.",
                        "x-plnnrly-server-assigned": true
                    },
                    "subscription_type": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/SubscriptionTypeSummary"
                            }
                        ],
                        "description": "The subscription type this template line belongs to."
                    },
                    "subscribable_type": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "enum": [
                            "subscription_product",
                            null
                        ],
                        "x-extensible-enum": true,
                        "description": "The kind of record `subscribable` points at; it says which branch of `subscribable` applies."
                    },
                    "subscribable_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "The id of the record `subscribable` points at."
                    },
                    "subscribable": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/SubscriptionProductSummary"
                            },
                            {
                                "type": "null"
                            }
                        ],
                        "x-plnnrly-discriminator": {
                            "propertyName": "subscribable_type",
                            "mapping": {
                                "subscription_product": "#/components/schemas/SubscriptionProductSummary"
                            }
                        },
                        "x-plnnrly-morph-aliases": [
                            "subscription_product"
                        ],
                        "description": "The record this template line sells, normally a subscription product. Null on a line that sells nothing in particular, and a bare {\"id\": N} reference with `subscribable_type` of `domain` or `server` on a line pointed at a hosting record, which only plnnrly itself can do. Expanding it with `?include=subscribable` additionally requires `subscription_products` read permission and a token scope covering that prefix."
                    },
                    "name": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Optional label for the template line. plnnrly's form does not show it, so most existing lines carry `null`; it exists for programmatic naming and is what `?q=` searches."
                    },
                    "description": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "x-plnnrly-multiline": true,
                        "description": "Invoice-line text template, copied verbatim onto the customer subscription's line and resolved only when that line is invoiced. Placeholder tokens survive the copy unresolved: {{$domain}}, {{$period}}, {{$product}}, {{$customer}}, {{$site}}, {{$server}}, plus the Dutch aliases {{$domein}}, {{$periode}} and {{$klant}}."
                    },
                    "quantity": {
                        "type": "integer",
                        "description": "Number of units the template line carries. Whole units: this is an integer, not the fractional quantity an invoice row uses."
                    },
                    "default_purchase_price": {
                        "type": "string",
                        "format": "decimal",
                        "pattern": "^-?\\d+(\\.\\d{1,2})?$",
                        "description": "Purchase price per unit, excluding VAT, copied onto a new customer subscription's line."
                    },
                    "default_sales_price": {
                        "type": "string",
                        "format": "decimal",
                        "pattern": "^-?\\d+(\\.\\d{1,2})?$",
                        "description": "Sales price per unit, excluding VAT, copied onto a new customer subscription's line. This is what eventually bills."
                    },
                    "vat_rate": {
                        "type": "integer",
                        "description": "VAT percentage as a whole number: 21 means 21%."
                    },
                    "sort_order": {
                        "type": "integer",
                        "description": "Position of the line within its subscription type, and the default sort key. Assigned on create; there is no way to reorder template lines through the API.",
                        "x-plnnrly-server-assigned": true
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "deleted_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "Soft-delete timestamp, `null` while live."
                    },
                    "links": {
                        "$ref": "#/components/schemas/SubscriptionTypeItemLinks"
                    }
                },
                "required": [
                    "id",
                    "subscription_type_id",
                    "subscription_type",
                    "subscribable_type",
                    "subscribable_id",
                    "subscribable",
                    "name",
                    "description",
                    "quantity",
                    "default_purchase_price",
                    "default_sales_price",
                    "vat_rate",
                    "sort_order",
                    "created_at",
                    "updated_at",
                    "deleted_at",
                    "links"
                ],
                "description": "One template line on a subscription type: what is sold, how many, at what purchase and sales price, and at what VAT percentage. A subscription type is a blueprint rather than a live subscription, so these lines bill nothing themselves: they are copied, by value, onto a customer subscription's own lines the moment a type is picked for it. Editing a template line afterwards does not reach the subscriptions already created from it."
            },
            "SubscriptionTypeItemLinks": {
                "type": "object",
                "properties": {
                    "self": {
                        "type": "string",
                        "format": "uri",
                        "description": "The canonical URI of this record."
                    }
                },
                "required": [
                    "self"
                ],
                "additionalProperties": false
            },
            "SubscriptionTypeItemList": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "subscription_type_id": {
                        "type": "integer",
                        "description": "The subscription type this template line belongs to. Taken from the path on create and never writable afterwards: a line cannot be moved to another type.",
                        "x-plnnrly-server-assigned": true
                    },
                    "subscription_type": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/SubscriptionTypeSummary"
                            }
                        ],
                        "description": "The subscription type this template line belongs to."
                    },
                    "subscribable_type": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "enum": [
                            "subscription_product",
                            null
                        ],
                        "x-extensible-enum": true,
                        "description": "The kind of record `subscribable` points at; it says which branch of `subscribable` applies."
                    },
                    "subscribable_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "The id of the record `subscribable` points at."
                    },
                    "subscribable": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/SubscriptionProductSummary"
                            },
                            {
                                "type": "null"
                            }
                        ],
                        "x-plnnrly-discriminator": {
                            "propertyName": "subscribable_type",
                            "mapping": {
                                "subscription_product": "#/components/schemas/SubscriptionProductSummary"
                            }
                        },
                        "x-plnnrly-morph-aliases": [
                            "subscription_product"
                        ],
                        "description": "The record this template line sells, normally a subscription product. Null on a line that sells nothing in particular, and a bare {\"id\": N} reference with `subscribable_type` of `domain` or `server` on a line pointed at a hosting record, which only plnnrly itself can do. Expanding it with `?include=subscribable` additionally requires `subscription_products` read permission and a token scope covering that prefix."
                    },
                    "name": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Optional label for the template line. plnnrly's form does not show it, so most existing lines carry `null`; it exists for programmatic naming and is what `?q=` searches."
                    },
                    "description": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "x-plnnrly-multiline": true,
                        "description": "Invoice-line text template, copied verbatim onto the customer subscription's line and resolved only when that line is invoiced. Placeholder tokens survive the copy unresolved: {{$domain}}, {{$period}}, {{$product}}, {{$customer}}, {{$site}}, {{$server}}, plus the Dutch aliases {{$domein}}, {{$periode}} and {{$klant}}."
                    },
                    "quantity": {
                        "type": "integer",
                        "description": "Number of units the template line carries. Whole units: this is an integer, not the fractional quantity an invoice row uses."
                    },
                    "default_purchase_price": {
                        "type": "string",
                        "format": "decimal",
                        "pattern": "^-?\\d+(\\.\\d{1,2})?$",
                        "description": "Purchase price per unit, excluding VAT, copied onto a new customer subscription's line."
                    },
                    "default_sales_price": {
                        "type": "string",
                        "format": "decimal",
                        "pattern": "^-?\\d+(\\.\\d{1,2})?$",
                        "description": "Sales price per unit, excluding VAT, copied onto a new customer subscription's line. This is what eventually bills."
                    },
                    "vat_rate": {
                        "type": "integer",
                        "description": "VAT percentage as a whole number: 21 means 21%."
                    },
                    "sort_order": {
                        "type": "integer",
                        "description": "Position of the line within its subscription type, and the default sort key. Assigned on create; there is no way to reorder template lines through the API.",
                        "x-plnnrly-server-assigned": true
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "deleted_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "Soft-delete timestamp, `null` while live."
                    },
                    "links": {
                        "$ref": "#/components/schemas/SubscriptionTypeItemLinks"
                    }
                },
                "required": [
                    "id",
                    "subscription_type_id",
                    "subscription_type",
                    "subscribable_type",
                    "subscribable_id",
                    "subscribable",
                    "name",
                    "description",
                    "quantity",
                    "default_purchase_price",
                    "default_sales_price",
                    "vat_rate",
                    "sort_order",
                    "created_at",
                    "updated_at",
                    "deleted_at",
                    "links"
                ],
                "description": "One template line on a subscription type: what is sold, how many, at what purchase and sales price, and at what VAT percentage. A subscription type is a blueprint rather than a live subscription, so these lines bill nothing themselves: they are copied, by value, onto a customer subscription's own lines the moment a type is picked for it. Editing a template line afterwards does not reach the subscriptions already created from it."
            },
            "SubscriptionTypeItemSummary": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "name": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Template line label; `null` on the many lines created without one in plnnrly."
                    }
                },
                "required": [
                    "id",
                    "name"
                ],
                "description": "A subscription-type template line, as referenced from another resource."
            },
            "SubscriptionTypeLinks": {
                "type": "object",
                "properties": {
                    "self": {
                        "type": "string",
                        "format": "uri",
                        "description": "The canonical URI of this record."
                    },
                    "items": {
                        "type": "string",
                        "format": "uri",
                        "description": "Template lines on this subscription type."
                    }
                },
                "required": [
                    "self",
                    "items"
                ],
                "additionalProperties": false
            },
            "SubscriptionTypeList": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Subscription type name. It is also the name every customer subscription of this type is displayed under, so renaming a type renames what its existing subscriptions read as. Names are not unique."
                    },
                    "description": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "x-plnnrly-multiline": true,
                        "description": "What subscriptions of this type cover, as free text."
                    },
                    "default_billing_interval": {
                        "type": "string",
                        "enum": [
                            "yearly",
                            "monthly",
                            "one-time"
                        ],
                        "x-extensible-enum": true,
                        "description": "Interval a new subscription of this type is billed at by default. Omitting it on create yields yearly, the stored default, not the monthly that plnnrly's form pre-selects, which is a form convenience the API deliberately does not reproduce."
                    },
                    "auto_renew": {
                        "type": "boolean",
                        "description": "Whether subscriptions created from this type renew automatically by default. The value is stored exactly as sent; nothing forces it to `false`."
                    },
                    "active": {
                        "type": "boolean",
                        "description": "Whether the type is offered in the catalogue. It is a catalogue flag only: nothing in the application filters on it, so deactivating a type does not stop existing subscriptions of that type from being renewed or invoiced."
                    },
                    "sort_order": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Position in the catalogue, assigned by the server on create as one past the highest existing position. It is the default sort key, and there is no way to reorder types through the API: dragging types into order in plnnrly has no API equivalent in v2.0.",
                        "x-plnnrly-server-assigned": true
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync. It also feeds the `ETag` revision, because a subscription type has no `version` field."
                    },
                    "deleted_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "Soft-delete timestamp, `null` while live."
                    },
                    "items_count": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Number of template lines on this subscription type."
                    },
                    "links": {
                        "$ref": "#/components/schemas/SubscriptionTypeLinks"
                    }
                },
                "required": [
                    "id",
                    "name",
                    "description",
                    "default_billing_interval",
                    "auto_renew",
                    "active",
                    "sort_order",
                    "created_at",
                    "updated_at",
                    "deleted_at",
                    "items_count",
                    "links"
                ],
                "description": "A subscription type: the organisation's hand-maintained template a customer subscription is instanced from, carrying the default billing interval and renewal behaviour new subscriptions start with. Its template lines live on the items sub-resource, and the type's name is the name plnnrly shows for every subscription of that type."
            },
            "SubscriptionTypeSummary": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Subscription type name."
                    }
                },
                "required": [
                    "id",
                    "name"
                ],
                "description": "A subscription type, as referenced from another resource."
            },
            "SubscriptionTypesActionsRestoreMeta": {
                "type": "object",
                "properties": {
                    "warnings": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "code": {
                                    "type": "string",
                                    "enum": [
                                        "already_in_target_state"
                                    ],
                                    "x-extensible-enum": true,
                                    "description": "Machine-readable warning code. Switch on this, never on detail."
                                },
                                "detail": {
                                    "type": "string",
                                    "description": "Human-readable explanation."
                                },
                                "pointer": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "description": "RFC 6901 pointer into the request body, where the warning is about a body field."
                                },
                                "parameter": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "description": "The query-parameter name, brackets included, where the warning is about a parameter."
                                },
                                "sunset": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "format": "date",
                                    "description": "Date the deprecated thing stops working."
                                },
                                "replacement": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "description": "What to use instead."
                                }
                            },
                            "required": [
                                "code",
                                "detail"
                            ],
                            "description": "An advisory about this request. It never changes the status code or `data`."
                        },
                        "description": "Non-fatal notes about this request. Switch on `code`, never on `detail`."
                    }
                },
                "additionalProperties": false
            },
            "SubscriptionTypesIndexMeta": {
                "type": "object",
                "properties": {
                    "page": {
                        "$ref": "#/components/schemas/Page"
                    }
                },
                "additionalProperties": false,
                "required": [
                    "page"
                ]
            },
            "SubscriptionTypesItemsIndexMeta": {
                "type": "object",
                "properties": {
                    "page": {
                        "$ref": "#/components/schemas/Page"
                    }
                },
                "additionalProperties": false,
                "required": [
                    "page"
                ]
            },
            "SubscriptionTypesShowMeta": {
                "type": "object",
                "properties": {
                    "actions": {
                        "type": "array",
                        "items": {
                            "type": "string",
                            "enum": [
                                "restore"
                            ]
                        },
                        "description": "The actions the caller may currently invoke on this record."
                    }
                },
                "additionalProperties": false
            },
            "Subtask": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "work_item_id": {
                        "type": "integer",
                        "description": "The work item whose checklist this subtask belongs to. Taken from the path on create and not writable.",
                        "x-plnnrly-server-assigned": true
                    },
                    "title": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Subtask title, as shown on the checklist row. This is the field `?q=` searches."
                    },
                    "description": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "html",
                        "description": "Optional note on the subtask. HTML from the rich editor, sanitised on read and on write. Null when the subtask is just a title."
                    },
                    "is_resolved": {
                        "type": "boolean",
                        "description": "Whether the subtask is ticked off."
                    },
                    "order": {
                        "type": "integer",
                        "description": "Position in the work item's checklist. Assigned on create as the next position in that checklist, changed only by dragging in plnnrly, and not writable.",
                        "x-plnnrly-server-assigned": true
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "work_item": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/WorkItemSummary"
                            }
                        ],
                        "description": "The work item this subtask hangs off."
                    },
                    "links": {
                        "$ref": "#/components/schemas/SubtaskLinks"
                    }
                },
                "required": [
                    "id",
                    "work_item_id",
                    "title",
                    "description",
                    "is_resolved",
                    "order",
                    "created_at",
                    "updated_at",
                    "work_item",
                    "links"
                ],
                "description": "A checklist subtask on a work item: a title, an optional rich-text note and a ticked-off flag, held in the work item's own checklist order. Subtasks only mean anything under the work item they belong to, so they are listed and created through that work item and read, edited and deleted on their own flat path. There is no organisation-wide collection of subtasks and no `filter[work_item_id]`."
            },
            "SubtaskLinks": {
                "type": "object",
                "properties": {
                    "self": {
                        "type": "string",
                        "format": "uri",
                        "description": "The canonical URI of this record."
                    }
                },
                "required": [
                    "self"
                ],
                "additionalProperties": false
            },
            "SubtaskList": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "work_item_id": {
                        "type": "integer",
                        "description": "The work item whose checklist this subtask belongs to. Taken from the path on create and not writable.",
                        "x-plnnrly-server-assigned": true
                    },
                    "title": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Subtask title, as shown on the checklist row. This is the field `?q=` searches."
                    },
                    "is_resolved": {
                        "type": "boolean",
                        "description": "Whether the subtask is ticked off."
                    },
                    "order": {
                        "type": "integer",
                        "description": "Position in the work item's checklist. Assigned on create as the next position in that checklist, changed only by dragging in plnnrly, and not writable.",
                        "x-plnnrly-server-assigned": true
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "work_item": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/WorkItemSummary"
                            }
                        ],
                        "description": "The work item this subtask hangs off."
                    },
                    "links": {
                        "$ref": "#/components/schemas/SubtaskLinks"
                    }
                },
                "required": [
                    "id",
                    "work_item_id",
                    "title",
                    "is_resolved",
                    "order",
                    "created_at",
                    "updated_at",
                    "work_item",
                    "links"
                ],
                "description": "A checklist subtask on a work item: a title, an optional rich-text note and a ticked-off flag, held in the work item's own checklist order. Subtasks only mean anything under the work item they belong to, so they are listed and created through that work item and read, edited and deleted on their own flat path. There is no organisation-wide collection of subtasks and no `filter[work_item_id]`."
            },
            "SubtaskSummary": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "title": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Subtask title."
                    },
                    "is_resolved": {
                        "type": "boolean",
                        "description": "Whether the subtask is ticked off."
                    }
                },
                "required": [
                    "id",
                    "title",
                    "is_resolved"
                ],
                "description": "A checklist subtask, as referenced from another resource."
            },
            "SubtaskSummaryInclude": {
                "type": "object",
                "properties": {
                    "data": {
                        "type": "array",
                        "items": {
                            "$ref": "#/components/schemas/SubtaskSummary"
                        },
                        "maxItems": 25
                    },
                    "meta": {
                        "type": "object",
                        "properties": {
                            "count": {
                                "type": "integer",
                                "minimum": 0
                            },
                            "truncated": {
                                "type": "boolean"
                            }
                        },
                        "required": [
                            "truncated"
                        ],
                        "additionalProperties": false
                    }
                },
                "required": [
                    "data",
                    "meta"
                ],
                "additionalProperties": false
            },
            "TagSummary": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "title": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Tag title as shown in the UI."
                    }
                },
                "required": [
                    "id",
                    "title"
                ],
                "description": "A tag, as referenced from another resource."
            },
            "TagSummaryInclude": {
                "type": "object",
                "properties": {
                    "data": {
                        "type": "array",
                        "items": {
                            "$ref": "#/components/schemas/TagSummary"
                        },
                        "maxItems": 50
                    },
                    "meta": {
                        "type": "object",
                        "properties": {
                            "count": {
                                "type": "integer",
                                "minimum": 0
                            },
                            "truncated": {
                                "type": "boolean"
                            }
                        },
                        "required": [
                            "truncated"
                        ],
                        "additionalProperties": false
                    }
                },
                "required": [
                    "data",
                    "meta"
                ],
                "additionalProperties": false
            },
            "Task": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "budget_id": {
                        "type": "integer",
                        "description": "Budget the task is filed under. This is the task's only link to a project (a task has no `project_id` of its own), and it is settable at create time only; a task cannot be re-budgeted through the API."
                    },
                    "user_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "The employee the task belongs to. Always the authenticated user on create; not writable. Null on a task left unassigned in plnnrly.",
                        "x-plnnrly-server-assigned": true
                    },
                    "team_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Team the task is filed under. Nothing on the API path assigns it, so a task created through the API always carries `null`; plnnrly itself is the only place a team is set. Not writable, and there is no `team` object beside it.",
                        "x-plnnrly-server-assigned": true
                    },
                    "due_date": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date",
                        "description": "Day the task is due. Required when creating a task through the API, and clearable afterwards; tasks created in plnnrly may carry `null`."
                    },
                    "minutes": {
                        "type": "integer",
                        "description": "Estimated effort, in minutes."
                    },
                    "description": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Short description shown on the kanban card. This is the task's label and the only field `?q=` searches."
                    },
                    "comment": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Optional longer comment on the card, capped at 255 characters."
                    },
                    "is_finished": {
                        "type": "boolean",
                        "description": "Whether the task is done. Cross-synced with `status` on every write: setting `status` to finished sets this `true` and any other status sets it `false`, while setting this `false` on a finished task moves `status` back to active. `status` wins when both are sent."
                    },
                    "is_billable": {
                        "type": "boolean",
                        "description": "Whether time logged on this task is billable. Defaults to `true` when a task is created through the API, regardless of the budget's own setting."
                    },
                    "status": {
                        "type": "string",
                        "enum": [
                            "on-hold",
                            "new",
                            "active",
                            "internal-test",
                            "external-test",
                            "finished"
                        ],
                        "x-extensible-enum": true,
                        "description": "Kanban column the card sits in."
                    },
                    "order_column": {
                        "type": "integer",
                        "description": "Position on the kanban board, lowest first. Appended to the end of the board on create and changed when someone drags the card in plnnrly. Not writable.",
                        "x-plnnrly-server-assigned": true
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "budget": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/BudgetSummary"
                            }
                        ],
                        "description": "The budget this task is filed under."
                    },
                    "user": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/UserSummary"
                            },
                            {
                                "type": "null"
                            }
                        ],
                        "description": "The employee the task belongs to."
                    },
                    "links": {
                        "$ref": "#/components/schemas/TaskLinks"
                    }
                },
                "required": [
                    "id",
                    "budget_id",
                    "user_id",
                    "team_id",
                    "due_date",
                    "minutes",
                    "description",
                    "comment",
                    "is_finished",
                    "is_billable",
                    "status",
                    "order_column",
                    "created_at",
                    "updated_at",
                    "budget",
                    "user",
                    "links"
                ],
                "description": "A kanban task: one card on an employee's board, filed under a budget of a project, carrying an estimate in minutes that time is logged against. Tasks are per-user, so a caller sees only their own unless they hold `kanban::manage_for_others`."
            },
            "TaskLinks": {
                "type": "object",
                "properties": {
                    "self": {
                        "type": "string",
                        "format": "uri",
                        "description": "The canonical URI of this record."
                    }
                },
                "required": [
                    "self"
                ],
                "additionalProperties": false
            },
            "TaskList": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "budget_id": {
                        "type": "integer",
                        "description": "Budget the task is filed under. This is the task's only link to a project (a task has no `project_id` of its own), and it is settable at create time only; a task cannot be re-budgeted through the API."
                    },
                    "user_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "The employee the task belongs to. Always the authenticated user on create; not writable. Null on a task left unassigned in plnnrly.",
                        "x-plnnrly-server-assigned": true
                    },
                    "team_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Team the task is filed under. Nothing on the API path assigns it, so a task created through the API always carries `null`; plnnrly itself is the only place a team is set. Not writable, and there is no `team` object beside it.",
                        "x-plnnrly-server-assigned": true
                    },
                    "due_date": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date",
                        "description": "Day the task is due. Required when creating a task through the API, and clearable afterwards; tasks created in plnnrly may carry `null`."
                    },
                    "minutes": {
                        "type": "integer",
                        "description": "Estimated effort, in minutes."
                    },
                    "description": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Short description shown on the kanban card. This is the task's label and the only field `?q=` searches."
                    },
                    "comment": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Optional longer comment on the card, capped at 255 characters."
                    },
                    "is_finished": {
                        "type": "boolean",
                        "description": "Whether the task is done. Cross-synced with `status` on every write: setting `status` to finished sets this `true` and any other status sets it `false`, while setting this `false` on a finished task moves `status` back to active. `status` wins when both are sent."
                    },
                    "is_billable": {
                        "type": "boolean",
                        "description": "Whether time logged on this task is billable. Defaults to `true` when a task is created through the API, regardless of the budget's own setting."
                    },
                    "status": {
                        "type": "string",
                        "enum": [
                            "on-hold",
                            "new",
                            "active",
                            "internal-test",
                            "external-test",
                            "finished"
                        ],
                        "x-extensible-enum": true,
                        "description": "Kanban column the card sits in."
                    },
                    "order_column": {
                        "type": "integer",
                        "description": "Position on the kanban board, lowest first. Appended to the end of the board on create and changed when someone drags the card in plnnrly. Not writable.",
                        "x-plnnrly-server-assigned": true
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "budget": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/BudgetSummary"
                            }
                        ],
                        "description": "The budget this task is filed under."
                    },
                    "user": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/UserSummary"
                            },
                            {
                                "type": "null"
                            }
                        ],
                        "description": "The employee the task belongs to."
                    },
                    "links": {
                        "$ref": "#/components/schemas/TaskLinks"
                    }
                },
                "required": [
                    "id",
                    "budget_id",
                    "user_id",
                    "team_id",
                    "due_date",
                    "minutes",
                    "description",
                    "comment",
                    "is_finished",
                    "is_billable",
                    "status",
                    "order_column",
                    "created_at",
                    "updated_at",
                    "budget",
                    "user",
                    "links"
                ],
                "description": "A kanban task: one card on an employee's board, filed under a budget of a project, carrying an estimate in minutes that time is logged against. Tasks are per-user, so a caller sees only their own unless they hold `kanban::manage_for_others`."
            },
            "TaskSummary": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "description": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Task description as shown in the UI."
                    }
                },
                "required": [
                    "id",
                    "description"
                ],
                "description": "A task, as referenced from another resource."
            },
            "TenantUnavailableProblem": {
                "type": "object",
                "properties": {
                    "type": {
                        "type": "string",
                        "format": "uri",
                        "const": "https://developers.plnnrly.com/api/v2/problems/tenant-unavailable",
                        "description": "The problem type. This is the only member a client switches on."
                    },
                    "title": {
                        "type": "string",
                        "description": "Short, stable per type, English, never interpolated. Never parse it."
                    },
                    "status": {
                        "type": "integer",
                        "const": 503,
                        "description": "The HTTP status, repeated in the body."
                    },
                    "detail": {
                        "type": "string",
                        "description": "Human-readable explanation. May quote values from your request; never shows internal error output."
                    },
                    "instance": {
                        "type": "string",
                        "description": "The request path, with no query string."
                    },
                    "request_id": {
                        "type": "string",
                        "description": "The id also in the X-Request-Id header. Quote it when you report a problem to plnnrly support."
                    }
                },
                "required": [
                    "type",
                    "title",
                    "status",
                    "detail",
                    "instance",
                    "request_id"
                ],
                "description": "Tenant unavailable"
            },
            "Time": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "date": {
                        "type": "string",
                        "format": "date",
                        "description": "The calendar day the work happened."
                    },
                    "minutes": {
                        "type": "integer",
                        "description": "Duration in minutes, between 1 and 1440."
                    },
                    "description": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "What the employee worked on."
                    },
                    "project_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Project the hours are booked against; `null` once that project is deleted."
                    },
                    "project": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/ProjectSummary"
                            },
                            {
                                "type": "null"
                            }
                        ],
                        "description": "The project the hours are booked against, always expanded. Its `customer_id` is the customer to fetch with `GET /api/v2/customers/{customer}`; the customer itself is not nested."
                    },
                    "budget_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Budget the hours are invoiced against. Derived from the task, never accepted on a write; `null` when no task is set or the budget is deleted.",
                        "x-plnnrly-server-assigned": true
                    },
                    "budget": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/BudgetSummary"
                            },
                            {
                                "type": "null"
                            }
                        ],
                        "description": "The budget the hours are invoiced against. Expanding it with `?include=budget` additionally requires the `budgets::read` permission and a token scope covering the budgets prefix."
                    },
                    "task_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Task the hours are booked on; `null` when the entry is not attached to one, or once that task is deleted."
                    },
                    "task": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/TaskSummary"
                            },
                            {
                                "type": "null"
                            }
                        ],
                        "description": "The task the hours are booked on. Expanding it with `?include=task` additionally requires the `kanban::manage_self` permission and a token scope covering the kanban prefix, and is limited to the tasks you can see on the tasks collection."
                    },
                    "work_item_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Work item the hours are attached to; `null` when the entry is not attached to one, or once that work item is deleted."
                    },
                    "work_item": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/WorkItemSummary"
                            },
                            {
                                "type": "null"
                            }
                        ],
                        "description": "The work item the hours are attached to, always expanded."
                    },
                    "user_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Employee the hours belong to. Assigned on create (from the authenticated user, or from `on_behalf_of_user_id`) and never writable afterwards; reassigning a worklog is a DELETE plus a POST.",
                        "x-plnnrly-server-assigned": true
                    },
                    "user": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/UserSummary"
                            },
                            {
                                "type": "null"
                            }
                        ],
                        "description": "The employee the hours belong to. Expanding it with `?include=user` additionally requires the `users::read` permission and a token scope covering the users prefix."
                    },
                    "is_not_billable": {
                        "type": "boolean",
                        "description": "Whether these hours are excluded from invoicing. Inferred from the task's `is_billable` flag (`false` without a task) on create and again whenever `task_id` is sent. Sending a value that differs from the inferred or current one requires `billable_change_reason` and puts the entry in a reviewer's queue; without it the write is a `422` on `/billable_change_reason`."
                    },
                    "is_overtime": {
                        "type": "boolean",
                        "description": "Whether the hours count as overtime. Sending it without the `times::write_overtime` permission leaves it `false` and adds an `input_ignored` warning rather than failing the write."
                    },
                    "exceeded_budget": {
                        "type": "boolean",
                        "description": "Whether the entry was booked knowing it exceeds its budget. Set on create only, and `true` whenever a `reason` is sent; setting it puts the entry in a reviewer's queue."
                    },
                    "reason": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "x-plnnrly-multiline": true,
                        "description": "Why the budget overrun was accepted. Sending it on create marks the entry as exceeding its budget. Once a reviewer has settled the entry it can no longer be changed: a PATCH that does answers `409` `reason_reviewed`."
                    },
                    "billable_change_reason": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "x-plnnrly-multiline": true,
                        "description": "Why the inferred billability was overridden. Required whenever `is_not_billable` is sent with a value that differs from the inferred or current one, on create and on PATCH alike; the override puts the entry in a reviewer's queue, reopening one a reviewer had already settled."
                    },
                    "reviewed_by": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "The reviewer who settled the entry, once one has. Set by the review queue in plnnrly; this API exposes no way to write it.",
                        "x-plnnrly-server-assigned": true
                    },
                    "invoice_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "The invoice these hours were billed on, once they have been. While it is set, the entry is locked: a write answers `409` with `conflict.reason` `locked_by_invoice`.",
                        "x-plnnrly-server-assigned": true
                    },
                    "sales_invoice_row_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "The sales-invoice line these hours were billed on, once they have been. Set by the invoicing flow.",
                        "x-plnnrly-server-assigned": true
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "links": {
                        "$ref": "#/components/schemas/TimeLinks"
                    }
                },
                "required": [
                    "id",
                    "date",
                    "minutes",
                    "description",
                    "project_id",
                    "project",
                    "budget_id",
                    "budget",
                    "task_id",
                    "task",
                    "work_item_id",
                    "work_item",
                    "user_id",
                    "user",
                    "is_not_billable",
                    "is_overtime",
                    "exceeded_budget",
                    "reason",
                    "billable_change_reason",
                    "reviewed_by",
                    "invoice_id",
                    "sales_invoice_row_id",
                    "created_at",
                    "updated_at",
                    "links"
                ],
                "description": "A worklog: minutes of work booked by one employee against one project on one calendar day, optionally attached to a task and a work item. Billability is inferred from the task, and overriding the inference needs a `billable_change_reason`. An entry that exceeds its budget, or whose billability was overridden, enters a reviewer's queue until a reviewer settles it; while it sits there, and forever after it has been invoiced, it is locked against changes."
            },
            "TimeLinks": {
                "type": "object",
                "properties": {
                    "self": {
                        "type": "string",
                        "format": "uri",
                        "description": "The canonical URI of this record."
                    }
                },
                "required": [
                    "self"
                ],
                "additionalProperties": false
            },
            "TimeList": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "date": {
                        "type": "string",
                        "format": "date",
                        "description": "The calendar day the work happened."
                    },
                    "minutes": {
                        "type": "integer",
                        "description": "Duration in minutes, between 1 and 1440."
                    },
                    "description": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "What the employee worked on."
                    },
                    "project_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Project the hours are booked against; `null` once that project is deleted."
                    },
                    "project": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/ProjectSummary"
                            },
                            {
                                "type": "null"
                            }
                        ],
                        "description": "The project the hours are booked against, always expanded. Its `customer_id` is the customer to fetch with `GET /api/v2/customers/{customer}`; the customer itself is not nested."
                    },
                    "budget_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Budget the hours are invoiced against. Derived from the task, never accepted on a write; `null` when no task is set or the budget is deleted.",
                        "x-plnnrly-server-assigned": true
                    },
                    "budget": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/BudgetSummary"
                            },
                            {
                                "type": "null"
                            }
                        ],
                        "description": "The budget the hours are invoiced against. Expanding it with `?include=budget` additionally requires the `budgets::read` permission and a token scope covering the budgets prefix."
                    },
                    "task_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Task the hours are booked on; `null` when the entry is not attached to one, or once that task is deleted."
                    },
                    "task": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/TaskSummary"
                            },
                            {
                                "type": "null"
                            }
                        ],
                        "description": "The task the hours are booked on. Expanding it with `?include=task` additionally requires the `kanban::manage_self` permission and a token scope covering the kanban prefix, and is limited to the tasks you can see on the tasks collection."
                    },
                    "work_item_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Work item the hours are attached to; `null` when the entry is not attached to one, or once that work item is deleted."
                    },
                    "work_item": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/WorkItemSummary"
                            },
                            {
                                "type": "null"
                            }
                        ],
                        "description": "The work item the hours are attached to, always expanded."
                    },
                    "user_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Employee the hours belong to. Assigned on create (from the authenticated user, or from `on_behalf_of_user_id`) and never writable afterwards; reassigning a worklog is a DELETE plus a POST.",
                        "x-plnnrly-server-assigned": true
                    },
                    "user": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/UserSummary"
                            },
                            {
                                "type": "null"
                            }
                        ],
                        "description": "The employee the hours belong to. Expanding it with `?include=user` additionally requires the `users::read` permission and a token scope covering the users prefix."
                    },
                    "is_not_billable": {
                        "type": "boolean",
                        "description": "Whether these hours are excluded from invoicing. Inferred from the task's `is_billable` flag (`false` without a task) on create and again whenever `task_id` is sent. Sending a value that differs from the inferred or current one requires `billable_change_reason` and puts the entry in a reviewer's queue; without it the write is a `422` on `/billable_change_reason`."
                    },
                    "is_overtime": {
                        "type": "boolean",
                        "description": "Whether the hours count as overtime. Sending it without the `times::write_overtime` permission leaves it `false` and adds an `input_ignored` warning rather than failing the write."
                    },
                    "exceeded_budget": {
                        "type": "boolean",
                        "description": "Whether the entry was booked knowing it exceeds its budget. Set on create only, and `true` whenever a `reason` is sent; setting it puts the entry in a reviewer's queue."
                    },
                    "reviewed_by": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "The reviewer who settled the entry, once one has. Set by the review queue in plnnrly; this API exposes no way to write it.",
                        "x-plnnrly-server-assigned": true
                    },
                    "invoice_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "The invoice these hours were billed on, once they have been. While it is set, the entry is locked: a write answers `409` with `conflict.reason` `locked_by_invoice`.",
                        "x-plnnrly-server-assigned": true
                    },
                    "sales_invoice_row_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "The sales-invoice line these hours were billed on, once they have been. Set by the invoicing flow.",
                        "x-plnnrly-server-assigned": true
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "links": {
                        "$ref": "#/components/schemas/TimeLinks"
                    }
                },
                "required": [
                    "id",
                    "date",
                    "minutes",
                    "description",
                    "project_id",
                    "project",
                    "budget_id",
                    "budget",
                    "task_id",
                    "task",
                    "work_item_id",
                    "work_item",
                    "user_id",
                    "user",
                    "is_not_billable",
                    "is_overtime",
                    "exceeded_budget",
                    "reviewed_by",
                    "invoice_id",
                    "sales_invoice_row_id",
                    "created_at",
                    "updated_at",
                    "links"
                ],
                "description": "A worklog: minutes of work booked by one employee against one project on one calendar day, optionally attached to a task and a work item. Billability is inferred from the task, and overriding the inference needs a `billable_change_reason`. An entry that exceeds its budget, or whose billability was overridden, enters a reviewer's queue until a reviewer settles it; while it sits there, and forever after it has been invoiced, it is locked against changes."
            },
            "TimeSummary": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "date": {
                        "type": "string",
                        "format": "date",
                        "description": "The calendar day the work happened."
                    },
                    "minutes": {
                        "type": "integer",
                        "description": "Duration in minutes."
                    }
                },
                "required": [
                    "id",
                    "date",
                    "minutes"
                ],
                "description": "A worklog, as referenced from another resource."
            },
            "Timer": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "user_id": {
                        "type": "integer",
                        "description": "Employee this timer belongs to. Always the authenticated user; a timer cannot be started for someone else.",
                        "x-plnnrly-server-assigned": true
                    },
                    "user": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/UserSummary"
                            }
                        ],
                        "description": "The employee this timer belongs to. Expanding it with `?include=user` additionally requires the `users::read` permission and a token scope covering the users prefix."
                    },
                    "timeable_type": {
                        "type": "string",
                        "enum": [
                            "work_item",
                            "event"
                        ],
                        "x-extensible-enum": true,
                        "description": "The kind of record `timeable` points at; it says which branch of `timeable` applies."
                    },
                    "timeable_id": {
                        "type": "integer",
                        "description": "The id of the record `timeable` points at."
                    },
                    "timeable": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/WorkItemSummary"
                            },
                            {
                                "$ref": "#/components/schemas/EventSummary"
                            }
                        ],
                        "x-plnnrly-discriminator": {
                            "propertyName": "timeable_type",
                            "mapping": {
                                "work_item": "#/components/schemas/WorkItemSummary",
                                "event": "#/components/schemas/EventSummary"
                            }
                        },
                        "x-plnnrly-morph-aliases": [
                            "work_item",
                            "event"
                        ],
                        "description": "The record being timed. Expanding it with `?include=timeable` additionally requires the read permission and token scope of the target's own prefix, per branch: `project_management` for a work item, events for a planning event."
                    },
                    "started_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "When the stopwatch was started. Elapsed time is this value against the client's own clock; the API publishes no elapsed counter."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "links": {
                        "$ref": "#/components/schemas/TimerLinks"
                    }
                },
                "required": [
                    "id",
                    "user_id",
                    "user",
                    "timeable_type",
                    "timeable_id",
                    "timeable",
                    "started_at",
                    "created_at",
                    "updated_at",
                    "links"
                ],
                "description": "A running stopwatch: one row per employee per timed record, created when the timer starts and deleted when it is discarded or its minutes are booked. There is at most one timer per employee-and-target pair. `started_at` is the only state a timer carries: elapsed time is derived from it by the client, which needs no server clock. A timer on a record type this API does not publish (an Outlook event, which the agenda really does time) renders `timeable` as the bare `{\"id\": N}` reference form, with `timeable_type` intact and no expansion available."
            },
            "TimerLinks": {
                "type": "object",
                "properties": {
                    "self": {
                        "type": "string",
                        "format": "uri",
                        "description": "The canonical URI of this record."
                    }
                },
                "required": [
                    "self"
                ],
                "additionalProperties": false
            },
            "TimerList": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "user_id": {
                        "type": "integer",
                        "description": "Employee this timer belongs to. Always the authenticated user; a timer cannot be started for someone else.",
                        "x-plnnrly-server-assigned": true
                    },
                    "user": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/UserSummary"
                            }
                        ],
                        "description": "The employee this timer belongs to. Expanding it with `?include=user` additionally requires the `users::read` permission and a token scope covering the users prefix."
                    },
                    "timeable_type": {
                        "type": "string",
                        "enum": [
                            "work_item",
                            "event"
                        ],
                        "x-extensible-enum": true,
                        "description": "The kind of record `timeable` points at; it says which branch of `timeable` applies."
                    },
                    "timeable_id": {
                        "type": "integer",
                        "description": "The id of the record `timeable` points at."
                    },
                    "timeable": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/WorkItemSummary"
                            },
                            {
                                "$ref": "#/components/schemas/EventSummary"
                            }
                        ],
                        "x-plnnrly-discriminator": {
                            "propertyName": "timeable_type",
                            "mapping": {
                                "work_item": "#/components/schemas/WorkItemSummary",
                                "event": "#/components/schemas/EventSummary"
                            }
                        },
                        "x-plnnrly-morph-aliases": [
                            "work_item",
                            "event"
                        ],
                        "description": "The record being timed. Expanding it with `?include=timeable` additionally requires the read permission and token scope of the target's own prefix, per branch: `project_management` for a work item, events for a planning event."
                    },
                    "started_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "When the stopwatch was started. Elapsed time is this value against the client's own clock; the API publishes no elapsed counter."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "links": {
                        "$ref": "#/components/schemas/TimerLinks"
                    }
                },
                "required": [
                    "id",
                    "user_id",
                    "user",
                    "timeable_type",
                    "timeable_id",
                    "timeable",
                    "started_at",
                    "created_at",
                    "updated_at",
                    "links"
                ],
                "description": "A running stopwatch: one row per employee per timed record, created when the timer starts and deleted when it is discarded or its minutes are booked. There is at most one timer per employee-and-target pair. `started_at` is the only state a timer carries: elapsed time is derived from it by the client, which needs no server clock. A timer on a record type this API does not publish (an Outlook event, which the agenda really does time) renders `timeable` as the bare `{\"id\": N}` reference form, with `timeable_type` intact and no expansion available."
            },
            "TimerSummary": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "started_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "When the stopwatch was started."
                    }
                },
                "required": [
                    "id",
                    "started_at"
                ],
                "description": "A running timer, as referenced from another resource."
            },
            "TimersIndexMeta": {
                "type": "object",
                "properties": {
                    "page": {
                        "$ref": "#/components/schemas/Page"
                    }
                },
                "additionalProperties": false,
                "required": [
                    "page"
                ]
            },
            "TodosIndexMeta": {
                "type": "object",
                "properties": {
                    "page": {
                        "$ref": "#/components/schemas/PageOptionalTotal"
                    }
                },
                "additionalProperties": false,
                "required": [
                    "page"
                ]
            },
            "TokenExpiredProblem": {
                "type": "object",
                "properties": {
                    "type": {
                        "type": "string",
                        "format": "uri",
                        "const": "https://developers.plnnrly.com/api/v2/problems/token-expired",
                        "description": "The problem type. This is the only member a client switches on."
                    },
                    "title": {
                        "type": "string",
                        "description": "Short, stable per type, English, never interpolated. Never parse it."
                    },
                    "status": {
                        "type": "integer",
                        "const": 401,
                        "description": "The HTTP status, repeated in the body."
                    },
                    "detail": {
                        "type": "string",
                        "description": "Human-readable explanation. May quote values from your request; never shows internal error output."
                    },
                    "instance": {
                        "type": "string",
                        "description": "The request path, with no query string."
                    },
                    "request_id": {
                        "type": "string",
                        "description": "The id also in the X-Request-Id header. Quote it when you report a problem to plnnrly support."
                    },
                    "expired_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "When the token expired."
                    }
                },
                "required": [
                    "type",
                    "title",
                    "status",
                    "detail",
                    "instance",
                    "request_id"
                ],
                "description": "Token expired"
            },
            "UnauthenticatedProblem": {
                "type": "object",
                "properties": {
                    "type": {
                        "type": "string",
                        "format": "uri",
                        "const": "https://developers.plnnrly.com/api/v2/problems/unauthenticated",
                        "description": "The problem type. This is the only member a client switches on."
                    },
                    "title": {
                        "type": "string",
                        "description": "Short, stable per type, English, never interpolated. Never parse it."
                    },
                    "status": {
                        "type": "integer",
                        "const": 401,
                        "description": "The HTTP status, repeated in the body."
                    },
                    "detail": {
                        "type": "string",
                        "description": "Human-readable explanation. May quote values from your request; never shows internal error output."
                    },
                    "instance": {
                        "type": "string",
                        "description": "The request path, with no query string."
                    },
                    "request_id": {
                        "type": "string",
                        "description": "The id also in the X-Request-Id header. Quote it when you report a problem to plnnrly support."
                    }
                },
                "required": [
                    "type",
                    "title",
                    "status",
                    "detail",
                    "instance",
                    "request_id"
                ],
                "description": "Unauthenticated"
            },
            "UnprocessableRelationshipProblem": {
                "type": "object",
                "properties": {
                    "type": {
                        "type": "string",
                        "format": "uri",
                        "const": "https://developers.plnnrly.com/api/v2/problems/unprocessable-relationship",
                        "description": "The problem type. This is the only member a client switches on."
                    },
                    "title": {
                        "type": "string",
                        "description": "Short, stable per type, English, never interpolated. Never parse it."
                    },
                    "status": {
                        "type": "integer",
                        "const": 422,
                        "description": "The HTTP status, repeated in the body."
                    },
                    "detail": {
                        "type": "string",
                        "description": "Human-readable explanation. May quote values from your request; never shows internal error output."
                    },
                    "instance": {
                        "type": "string",
                        "description": "The request path, with no query string."
                    },
                    "request_id": {
                        "type": "string",
                        "description": "The id also in the X-Request-Id header. Quote it when you report a problem to plnnrly support."
                    },
                    "errors": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "pointer": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "description": "RFC 6901 pointer into the request body, e.g. `/minutes`."
                                },
                                "parameter": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "description": "The query-parameter name, brackets included, e.g. `filter[start_date][gte]`."
                                },
                                "code": {
                                    "type": "string",
                                    "enum": [
                                        "unknown_parameter",
                                        "unsupported_parameter",
                                        "unknown_filter",
                                        "unknown_operator",
                                        "unknown_sort_key",
                                        "unknown_include_path",
                                        "depth_exceeded",
                                        "count_exceeded",
                                        "out_of_range",
                                        "invalid_format",
                                        "paired_filter_missing",
                                        "mutually_exclusive",
                                        "required",
                                        "integer",
                                        "numeric",
                                        "string",
                                        "boolean",
                                        "array",
                                        "min",
                                        "max",
                                        "between",
                                        "size",
                                        "distinct",
                                        "date_format",
                                        "decimal",
                                        "email",
                                        "url",
                                        "uuid",
                                        "regex",
                                        "after",
                                        "after_or_equal",
                                        "before",
                                        "exists",
                                        "unique",
                                        "required_with",
                                        "prohibited",
                                        "in"
                                    ],
                                    "x-extensible-enum": true,
                                    "description": "Machine-readable fault code. Switch on this, never on detail."
                                },
                                "detail": {
                                    "type": "string",
                                    "description": "Human-readable explanation; enumerates the allowed set for an allow-list failure."
                                }
                            },
                            "required": [
                                "code",
                                "detail"
                            ]
                        },
                        "description": "One entry per fault. Each carries exactly one of `pointer` or `parameter`."
                    }
                },
                "required": [
                    "type",
                    "title",
                    "status",
                    "detail",
                    "instance",
                    "request_id"
                ],
                "description": "Unprocessable relationship"
            },
            "UnsupportedMediaTypeProblem": {
                "type": "object",
                "properties": {
                    "type": {
                        "type": "string",
                        "format": "uri",
                        "const": "https://developers.plnnrly.com/api/v2/problems/unsupported-media-type",
                        "description": "The problem type. This is the only member a client switches on."
                    },
                    "title": {
                        "type": "string",
                        "description": "Short, stable per type, English, never interpolated. Never parse it."
                    },
                    "status": {
                        "type": "integer",
                        "const": 415,
                        "description": "The HTTP status, repeated in the body."
                    },
                    "detail": {
                        "type": "string",
                        "description": "Human-readable explanation. May quote values from your request; never shows internal error output."
                    },
                    "instance": {
                        "type": "string",
                        "description": "The request path, with no query string."
                    },
                    "request_id": {
                        "type": "string",
                        "description": "The id also in the X-Request-Id header. Quote it when you report a problem to plnnrly support."
                    }
                },
                "required": [
                    "type",
                    "title",
                    "status",
                    "detail",
                    "instance",
                    "request_id"
                ],
                "description": "Unsupported media type"
            },
            "UnsupportedParameterProblem": {
                "type": "object",
                "properties": {
                    "type": {
                        "type": "string",
                        "format": "uri",
                        "const": "https://developers.plnnrly.com/api/v2/problems/unsupported-parameter",
                        "description": "The problem type. This is the only member a client switches on."
                    },
                    "title": {
                        "type": "string",
                        "description": "Short, stable per type, English, never interpolated. Never parse it."
                    },
                    "status": {
                        "type": "integer",
                        "const": 400,
                        "description": "The HTTP status, repeated in the body."
                    },
                    "detail": {
                        "type": "string",
                        "description": "Human-readable explanation. May quote values from your request; never shows internal error output."
                    },
                    "instance": {
                        "type": "string",
                        "description": "The request path, with no query string."
                    },
                    "request_id": {
                        "type": "string",
                        "description": "The id also in the X-Request-Id header. Quote it when you report a problem to plnnrly support."
                    },
                    "errors": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "pointer": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "description": "RFC 6901 pointer into the request body, e.g. `/minutes`."
                                },
                                "parameter": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "description": "The query-parameter name, brackets included, e.g. `filter[start_date][gte]`."
                                },
                                "code": {
                                    "type": "string",
                                    "enum": [
                                        "unknown_parameter",
                                        "unsupported_parameter",
                                        "unknown_filter",
                                        "unknown_operator",
                                        "unknown_sort_key",
                                        "unknown_include_path",
                                        "depth_exceeded",
                                        "count_exceeded",
                                        "out_of_range",
                                        "invalid_format",
                                        "paired_filter_missing",
                                        "mutually_exclusive",
                                        "required",
                                        "integer",
                                        "numeric",
                                        "string",
                                        "boolean",
                                        "array",
                                        "min",
                                        "max",
                                        "between",
                                        "size",
                                        "distinct",
                                        "date_format",
                                        "decimal",
                                        "email",
                                        "url",
                                        "uuid",
                                        "regex",
                                        "after",
                                        "after_or_equal",
                                        "before",
                                        "exists",
                                        "unique",
                                        "required_with",
                                        "prohibited",
                                        "in"
                                    ],
                                    "x-extensible-enum": true,
                                    "description": "Machine-readable fault code. Switch on this, never on detail."
                                },
                                "detail": {
                                    "type": "string",
                                    "description": "Human-readable explanation; enumerates the allowed set for an allow-list failure."
                                }
                            },
                            "required": [
                                "code",
                                "detail"
                            ]
                        },
                        "description": "One entry per fault. Each carries exactly one of `pointer` or `parameter`."
                    }
                },
                "required": [
                    "type",
                    "title",
                    "status",
                    "detail",
                    "instance",
                    "request_id"
                ],
                "description": "Unsupported parameter"
            },
            "Upload": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier. Send this as `upload_id` when you register the bytes."
                    },
                    "work_item_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "The work item this ticket attaches to, or `null` when it targets a drive item."
                    },
                    "drive_item_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "The drive item this ticket attaches to, or `null` when it targets a work item."
                    },
                    "collection": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "The media collection the file will land in. Always `attachments`: it is chosen by the route, and no request field can change it."
                    },
                    "file_name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "The sanitized file name the file will carry. Sent when the ticket is created; control characters are stripped and `#`, `/`, `\\` and spaces become hyphens, so what you read back can differ from what you sent."
                    },
                    "byte_size": {
                        "type": "integer",
                        "description": "The size you declared when creating the ticket. A declaration, not a bound: the server counts the bytes it actually receives."
                    },
                    "checksum_sha256": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "The optional SHA-256 you declared when creating the ticket. When present the server refuses bytes that do not match it."
                    },
                    "received_byte_size": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "The number of bytes that actually arrived, measured by the server. Null until the upload lands."
                    },
                    "received_checksum_sha256": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "The SHA-256 the server computed over the bytes it received. Null until the upload lands."
                    },
                    "mime_type": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "The media type detected from the received bytes. Detected, never declared: the API accepts no content type when creating the ticket and ignores the `Content-Type` of the upload itself."
                    },
                    "status": {
                        "type": "string",
                        "enum": [
                            "pending",
                            "uploaded",
                            "committing",
                            "committed",
                            "expired",
                            "revoked"
                        ],
                        "x-extensible-enum": true,
                        "description": "Where the ticket is in its lifecycle."
                    },
                    "upload_url": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Where to PUT the file. An opaque absolute URL: send the raw file as the body of a PUT whose only header is your Authorization bearer token. No `Content-Type` is read and the body is stored exactly as sent, so a form envelope would become part of the file. Present only while status is `pending`."
                    },
                    "expires_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "The deadline for the PUT. After it the ticket is dead and a new one must be created."
                    },
                    "register_by": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "The deadline for the registration, set when the bytes land and measured from that moment, so a slow upload does not eat into the time you have to register. Null until the upload lands."
                    },
                    "uploaded_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "When the bytes arrived. Null until they do."
                    },
                    "committed_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "When the bytes became a file. Null until they do."
                    },
                    "media_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "The id of the file this ticket became. Null until it is registered, and set exactly once."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "When the ticket was created."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "links": {
                        "$ref": "#/components/schemas/UploadLinks"
                    }
                },
                "required": [
                    "id",
                    "work_item_id",
                    "drive_item_id",
                    "collection",
                    "file_name",
                    "byte_size",
                    "checksum_sha256",
                    "received_byte_size",
                    "received_checksum_sha256",
                    "mime_type",
                    "status",
                    "upload_url",
                    "expires_at",
                    "register_by",
                    "uploaded_at",
                    "committed_at",
                    "media_id",
                    "created_at",
                    "updated_at",
                    "links"
                ],
                "description": "An upload ticket: the server's permission for you to place one file's bytes in storage, and the record of what arrived. Create one against the record you want to attach to, PUT the raw bytes to `upload_url`, then POST its `id` as `upload_id` to that record's file collection to turn the bytes into a file. Tickets are yours alone, are single-use, expire, and can be deleted while unregistered.",
                "x-plnnrly-omitted": {
                    "created_by_id": "A ticket is bound to its issuer and a caller only ever sees their own.",
                    "disk": "Storage location chosen by plnnrly; a caller-chosen one could write outside your organisation.",
                    "model_id": "The other half of that target reference, withheld with it.",
                    "model_type": "Published as `work_item_id` or `drive_item_id`, the target the ticket was created on.",
                    "organization_id": "The organisation is implied by the host you authenticated against.",
                    "permission_prefix": "The permission family the ticket was created under; checked again on every step.",
                    "storage_key": "Server-derived storage key; publishing it would let a caller reach the staged bytes directly.",
                    "storage_token": "The unguessable segment of the storage key; it is never disclosed.",
                    "ulid": "The secret part of `upload_url`; `id` is the ticket's public identifier."
                }
            },
            "UploadLinks": {
                "type": "object",
                "properties": {
                    "self": {
                        "type": "string",
                        "format": "uri",
                        "description": "The canonical URI of this record."
                    }
                },
                "required": [
                    "self"
                ],
                "additionalProperties": false
            },
            "UploadList": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier. Send this as `upload_id` when you register the bytes."
                    },
                    "work_item_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "The work item this ticket attaches to, or `null` when it targets a drive item."
                    },
                    "drive_item_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "The drive item this ticket attaches to, or `null` when it targets a work item."
                    },
                    "collection": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "The media collection the file will land in. Always `attachments`: it is chosen by the route, and no request field can change it."
                    },
                    "file_name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "The sanitized file name the file will carry. Sent when the ticket is created; control characters are stripped and `#`, `/`, `\\` and spaces become hyphens, so what you read back can differ from what you sent."
                    },
                    "byte_size": {
                        "type": "integer",
                        "description": "The size you declared when creating the ticket. A declaration, not a bound: the server counts the bytes it actually receives."
                    },
                    "checksum_sha256": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "The optional SHA-256 you declared when creating the ticket. When present the server refuses bytes that do not match it."
                    },
                    "received_byte_size": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "The number of bytes that actually arrived, measured by the server. Null until the upload lands."
                    },
                    "received_checksum_sha256": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "The SHA-256 the server computed over the bytes it received. Null until the upload lands."
                    },
                    "mime_type": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "The media type detected from the received bytes. Detected, never declared: the API accepts no content type when creating the ticket and ignores the `Content-Type` of the upload itself."
                    },
                    "status": {
                        "type": "string",
                        "enum": [
                            "pending",
                            "uploaded",
                            "committing",
                            "committed",
                            "expired",
                            "revoked"
                        ],
                        "x-extensible-enum": true,
                        "description": "Where the ticket is in its lifecycle."
                    },
                    "upload_url": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Where to PUT the file. An opaque absolute URL: send the raw file as the body of a PUT whose only header is your Authorization bearer token. No `Content-Type` is read and the body is stored exactly as sent, so a form envelope would become part of the file. Present only while status is `pending`."
                    },
                    "expires_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "The deadline for the PUT. After it the ticket is dead and a new one must be created."
                    },
                    "register_by": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "The deadline for the registration, set when the bytes land and measured from that moment, so a slow upload does not eat into the time you have to register. Null until the upload lands."
                    },
                    "uploaded_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "When the bytes arrived. Null until they do."
                    },
                    "committed_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "When the bytes became a file. Null until they do."
                    },
                    "media_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "The id of the file this ticket became. Null until it is registered, and set exactly once."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "When the ticket was created."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "links": {
                        "$ref": "#/components/schemas/UploadLinks"
                    }
                },
                "required": [
                    "id",
                    "work_item_id",
                    "drive_item_id",
                    "collection",
                    "file_name",
                    "byte_size",
                    "checksum_sha256",
                    "received_byte_size",
                    "received_checksum_sha256",
                    "mime_type",
                    "status",
                    "upload_url",
                    "expires_at",
                    "register_by",
                    "uploaded_at",
                    "committed_at",
                    "media_id",
                    "created_at",
                    "updated_at",
                    "links"
                ],
                "description": "An upload ticket: the server's permission for you to place one file's bytes in storage, and the record of what arrived. Create one against the record you want to attach to, PUT the raw bytes to `upload_url`, then POST its `id` as `upload_id` to that record's file collection to turn the bytes into a file. Tickets are yours alone, are single-use, expire, and can be deleted while unregistered."
            },
            "User": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Full name as shown throughout plnnrly. Searchable with `?q=`."
                    },
                    "email": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Work email address, unique within the organisation. Searchable with `?q=`, which is the practical way to resolve a person to an id."
                    },
                    "color": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "The user's identifying colour, as a CSS colour string. Used by plnnrly's calendars and avatars; render it or ignore it."
                    },
                    "job_title": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Job title, e.g. Project Manager."
                    },
                    "phone_number": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Phone number as entered, in no normalised format."
                    },
                    "timezone": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "The user's own IANA timezone, e.g. Europe/Amsterdam. Null means the organisation's default applies. Every timestamp in this API is UTC regardless; this is the zone a client renders them in for this person."
                    },
                    "date_employed": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date",
                        "description": "First day of employment, or `null` where it was never recorded. A calendar date with no clock, so it is never converted to UTC."
                    },
                    "birthday": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date",
                        "description": "Date of birth, or `null` where it was never recorded."
                    },
                    "profitability_target_percentage": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Share of available hours this person is expected to bill, 1 to 100, or `null` where no target is set. Read by the profitability report; a user without a target is still reported on, just not judged against one."
                    },
                    "working_hours": {
                        "type": [
                            "array",
                            "null"
                        ],
                        "items": {
                            "type": "object",
                            "properties": {
                                "day": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "maxLength": 9,
                                    "description": "Weekday the shift falls on: one of monday, tuesday, wednesday, thursday, friday, saturday, sunday."
                                },
                                "start_time": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "maxLength": 5,
                                    "description": "Shift start as `HH:MM`."
                                },
                                "end_time": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "maxLength": 5,
                                    "description": "Shift end as `HH:MM`, always after the start. A shift never wraps past midnight: a night shift is two entries, 22:00-24:00 on one day and 00:00-06:00 on the next."
                                }
                            },
                            "required": [],
                            "description": "One working range on one weekday."
                        },
                        "description": "The contracted working week, one entry per shift, in week order. A weekday may carry several entries (a split day is two ranges), and a weekday that is not worked simply has none. Null or empty means no schedule has been recorded."
                    },
                    "work_locations": {
                        "type": "object",
                        "properties": {
                            "monday": {
                                "type": "string",
                                "enum": [
                                    "office",
                                    "remote",
                                    "client"
                                ],
                                "x-extensible-enum": true,
                                "description": "Monday."
                            },
                            "tuesday": {
                                "type": "string",
                                "enum": [
                                    "office",
                                    "remote",
                                    "client"
                                ],
                                "x-extensible-enum": true,
                                "description": "Tuesday."
                            },
                            "wednesday": {
                                "type": "string",
                                "enum": [
                                    "office",
                                    "remote",
                                    "client"
                                ],
                                "x-extensible-enum": true,
                                "description": "Wednesday."
                            },
                            "thursday": {
                                "type": "string",
                                "enum": [
                                    "office",
                                    "remote",
                                    "client"
                                ],
                                "x-extensible-enum": true,
                                "description": "Thursday."
                            },
                            "friday": {
                                "type": "string",
                                "enum": [
                                    "office",
                                    "remote",
                                    "client"
                                ],
                                "x-extensible-enum": true,
                                "description": "Friday."
                            },
                            "saturday": {
                                "type": "string",
                                "enum": [
                                    "office",
                                    "remote",
                                    "client"
                                ],
                                "x-extensible-enum": true,
                                "description": "Saturday."
                            },
                            "sunday": {
                                "type": "string",
                                "enum": [
                                    "office",
                                    "remote",
                                    "client"
                                ],
                                "x-extensible-enum": true,
                                "description": "Sunday."
                            }
                        },
                        "required": [
                            "monday",
                            "tuesday",
                            "wednesday",
                            "thursday",
                            "friday",
                            "saturday",
                            "sunday"
                        ],
                        "description": "Where the user usually works on each weekday, keyed monday through sunday. The stored pattern lists only the days away from the office, so a weekday it does not name reads as office here. A single day's override from the agenda is not reflected."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "deleted_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "Soft-delete timestamp, `null` while the user is active."
                    },
                    "links": {
                        "$ref": "#/components/schemas/UserLinks"
                    }
                },
                "required": [
                    "id",
                    "name",
                    "email",
                    "color",
                    "job_title",
                    "phone_number",
                    "timezone",
                    "date_employed",
                    "birthday",
                    "profitability_target_percentage",
                    "working_hours",
                    "work_locations",
                    "created_at",
                    "updated_at",
                    "deleted_at",
                    "links"
                ],
                "description": "A user: one member of the organisation, as plnnrly's people list shows them. Read-only: the API creates, updates and deletes no user, because password setting belongs to the invitation and reset flows and because roles, permissions and teams are out of scope as resources and as writable relations. Service accounts are never returned: a system user is absent from the collection and `404` by id. A soft-deleted user stays readable with `trashed=with`, which is how a sync client learns an employee left.",
                "x-plnnrly-omitted": {
                    "infoscreen": "The user's info screen choice in plnnrly; meaningless to an API client.",
                    "settings": "Per-user display preferences of the plnnrly app, with no meaning for an API client."
                }
            },
            "UserLinks": {
                "type": "object",
                "properties": {
                    "self": {
                        "type": "string",
                        "format": "uri",
                        "description": "The canonical URI of this record."
                    }
                },
                "required": [
                    "self"
                ],
                "additionalProperties": false
            },
            "UserList": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Full name as shown throughout plnnrly. Searchable with `?q=`."
                    },
                    "email": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Work email address, unique within the organisation. Searchable with `?q=`, which is the practical way to resolve a person to an id."
                    },
                    "color": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "The user's identifying colour, as a CSS colour string. Used by plnnrly's calendars and avatars; render it or ignore it."
                    },
                    "job_title": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Job title, e.g. Project Manager."
                    },
                    "phone_number": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Phone number as entered, in no normalised format."
                    },
                    "timezone": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "The user's own IANA timezone, e.g. Europe/Amsterdam. Null means the organisation's default applies. Every timestamp in this API is UTC regardless; this is the zone a client renders them in for this person."
                    },
                    "date_employed": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date",
                        "description": "First day of employment, or `null` where it was never recorded. A calendar date with no clock, so it is never converted to UTC."
                    },
                    "birthday": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date",
                        "description": "Date of birth, or `null` where it was never recorded."
                    },
                    "profitability_target_percentage": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Share of available hours this person is expected to bill, 1 to 100, or `null` where no target is set. Read by the profitability report; a user without a target is still reported on, just not judged against one."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "deleted_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "Soft-delete timestamp, `null` while the user is active."
                    },
                    "links": {
                        "$ref": "#/components/schemas/UserLinks"
                    }
                },
                "required": [
                    "id",
                    "name",
                    "email",
                    "color",
                    "job_title",
                    "phone_number",
                    "timezone",
                    "date_employed",
                    "birthday",
                    "profitability_target_percentage",
                    "created_at",
                    "updated_at",
                    "deleted_at",
                    "links"
                ],
                "description": "A user: one member of the organisation, as plnnrly's people list shows them. Read-only: the API creates, updates and deletes no user, because password setting belongs to the invitation and reset flows and because roles, permissions and teams are out of scope as resources and as writable relations. Service accounts are never returned: a system user is absent from the collection and `404` by id. A soft-deleted user stays readable with `trashed=with`, which is how a sync client learns an employee left."
            },
            "UserSummary": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Full name as shown in the UI."
                    }
                },
                "required": [
                    "id",
                    "name"
                ],
                "description": "A user, as referenced from another resource."
            },
            "UserTodo": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "user_id": {
                        "type": "integer",
                        "description": "The user the todo belongs to. Always the authenticated caller: this collection has no way to read or write another user's todos.",
                        "x-plnnrly-server-assigned": true
                    },
                    "drive_item_id": {
                        "type": "integer",
                        "description": "The drive item whose document this todo was parsed out of. Writable on create only; a todo cannot be re-parented. No relation object is exposed for it; read the document with `GET /api/v2/drive-items/{drive_item}`, which applies the drive access rules this collection does not. Archiving a drive item leaves its todos behind, so this id can name an item you can no longer read."
                    },
                    "todo_content": {
                        "type": "string",
                        "x-plnnrly-multiline": true,
                        "description": "The todo line as plain text."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "user": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/UserSummary"
                            }
                        ],
                        "description": "The user the todo belongs to, always the authenticated caller. Expanding it with `?include=user` additionally requires the `users::read` permission and a token scope covering the users prefix."
                    },
                    "links": {
                        "$ref": "#/components/schemas/UserTodoLinks"
                    }
                },
                "required": [
                    "id",
                    "user_id",
                    "drive_item_id",
                    "todo_content",
                    "created_at",
                    "updated_at",
                    "user",
                    "links"
                ],
                "description": "A todo: one unchecked task-list line in a drive document that mentions you. This is the list behind the Todos dashboard in plnnrly. ⚠ The collection is a derived copy of document content, not user-authored data: every write to a drive item rebuilds the whole todo set for that item from its HTML, deleting and re-creating the todos, so a todo has no stable identity across an edit of its source document and anything written through this collection is discarded on the next such edit. Completing a todo for real means unchecking-to-checking the task item in the document. The collection is hard-scoped to the authenticated caller with no escape: there is no permission, filter value or include that reads another user's todos."
            },
            "UserTodoLinks": {
                "type": "object",
                "properties": {
                    "self": {
                        "type": "string",
                        "format": "uri",
                        "description": "The canonical URI of this record."
                    }
                },
                "required": [
                    "self"
                ],
                "additionalProperties": false
            },
            "UserTodoList": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "user_id": {
                        "type": "integer",
                        "description": "The user the todo belongs to. Always the authenticated caller: this collection has no way to read or write another user's todos.",
                        "x-plnnrly-server-assigned": true
                    },
                    "drive_item_id": {
                        "type": "integer",
                        "description": "The drive item whose document this todo was parsed out of. Writable on create only; a todo cannot be re-parented. No relation object is exposed for it; read the document with `GET /api/v2/drive-items/{drive_item}`, which applies the drive access rules this collection does not. Archiving a drive item leaves its todos behind, so this id can name an item you can no longer read."
                    },
                    "todo_content": {
                        "type": "string",
                        "x-plnnrly-multiline": true,
                        "description": "The todo line as plain text."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "user": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/UserSummary"
                            }
                        ],
                        "description": "The user the todo belongs to, always the authenticated caller. Expanding it with `?include=user` additionally requires the `users::read` permission and a token scope covering the users prefix."
                    },
                    "links": {
                        "$ref": "#/components/schemas/UserTodoLinks"
                    }
                },
                "required": [
                    "id",
                    "user_id",
                    "drive_item_id",
                    "todo_content",
                    "created_at",
                    "updated_at",
                    "user",
                    "links"
                ],
                "description": "A todo: one unchecked task-list line in a drive document that mentions you. This is the list behind the Todos dashboard in plnnrly. ⚠ The collection is a derived copy of document content, not user-authored data: every write to a drive item rebuilds the whole todo set for that item from its HTML, deleting and re-creating the todos, so a todo has no stable identity across an edit of its source document and anything written through this collection is discarded on the next such edit. Completing a todo for real means unchecking-to-checking the task item in the document. The collection is hard-scoped to the authenticated caller with no escape: there is no permission, filter value or include that reads another user's todos."
            },
            "UserTodoSummary": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "todo_content": {
                        "type": "string",
                        "x-plnnrly-multiline": true,
                        "description": "The todo line as plain text."
                    }
                },
                "required": [
                    "id",
                    "todo_content"
                ],
                "description": "A todo, as referenced from another resource."
            },
            "UsersIndexMeta": {
                "type": "object",
                "properties": {
                    "page": {
                        "$ref": "#/components/schemas/Page"
                    }
                },
                "additionalProperties": false,
                "required": [
                    "page"
                ]
            },
            "ValidationFailedProblem": {
                "type": "object",
                "properties": {
                    "type": {
                        "type": "string",
                        "format": "uri",
                        "const": "https://developers.plnnrly.com/api/v2/problems/validation-failed",
                        "description": "The problem type. This is the only member a client switches on."
                    },
                    "title": {
                        "type": "string",
                        "description": "Short, stable per type, English, never interpolated. Never parse it."
                    },
                    "status": {
                        "type": "integer",
                        "const": 422,
                        "description": "The HTTP status, repeated in the body."
                    },
                    "detail": {
                        "type": "string",
                        "description": "Human-readable explanation. May quote values from your request; never shows internal error output."
                    },
                    "instance": {
                        "type": "string",
                        "description": "The request path, with no query string."
                    },
                    "request_id": {
                        "type": "string",
                        "description": "The id also in the X-Request-Id header. Quote it when you report a problem to plnnrly support."
                    },
                    "errors": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "pointer": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "description": "RFC 6901 pointer into the request body, e.g. `/minutes`."
                                },
                                "parameter": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "description": "The query-parameter name, brackets included, e.g. `filter[start_date][gte]`."
                                },
                                "code": {
                                    "type": "string",
                                    "enum": [
                                        "unknown_parameter",
                                        "unsupported_parameter",
                                        "unknown_filter",
                                        "unknown_operator",
                                        "unknown_sort_key",
                                        "unknown_include_path",
                                        "depth_exceeded",
                                        "count_exceeded",
                                        "out_of_range",
                                        "invalid_format",
                                        "paired_filter_missing",
                                        "mutually_exclusive",
                                        "required",
                                        "integer",
                                        "numeric",
                                        "string",
                                        "boolean",
                                        "array",
                                        "min",
                                        "max",
                                        "between",
                                        "size",
                                        "distinct",
                                        "date_format",
                                        "decimal",
                                        "email",
                                        "url",
                                        "uuid",
                                        "regex",
                                        "after",
                                        "after_or_equal",
                                        "before",
                                        "exists",
                                        "unique",
                                        "required_with",
                                        "prohibited",
                                        "in"
                                    ],
                                    "x-extensible-enum": true,
                                    "description": "Machine-readable fault code. Switch on this, never on detail."
                                },
                                "detail": {
                                    "type": "string",
                                    "description": "Human-readable explanation; enumerates the allowed set for an allow-list failure."
                                }
                            },
                            "required": [
                                "code",
                                "detail"
                            ]
                        },
                        "description": "One entry per fault. Each carries exactly one of `pointer` or `parameter`."
                    }
                },
                "required": [
                    "type",
                    "title",
                    "status",
                    "detail",
                    "instance",
                    "request_id"
                ],
                "description": "Validation failed"
            },
            "WorkItem": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "issue_key": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Human-readable key, e.g. `ACM-123`, allocated by the server from the project's customer code and a per-customer sequence. It also works in the path: GET `work-items/ACM-123` works. This is one of the two fields `?q=` searches.",
                        "x-plnnrly-server-assigned": true
                    },
                    "title": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Work item title. The other field `?q=` searches."
                    },
                    "description": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "html",
                        "description": "Work item description. HTML from the rich editor, sanitised on read and on write. Mention spans are preserved verbatim, so pass them back unchanged. Detail view only."
                    },
                    "priority": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "enum": [
                            "highest",
                            "high",
                            "normal",
                            "low",
                            "lowest",
                            null
                        ],
                        "x-extensible-enum": true,
                        "description": "Relative priority. Defaults to normal on create; `null` only on a legacy item that was never prioritised."
                    },
                    "due_date": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date",
                        "description": "Due date, a zone-free calendar date. Null when no date was set."
                    },
                    "estimation": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Original estimate, in minutes."
                    },
                    "remaining_estimate": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Remaining estimate, in minutes. Null where none was entered, in which case plnnrly derives one from the estimate and the logged time."
                    },
                    "location_name": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Name of the place the work happens, as picked from the address search in plnnrly. Null when no location was set. Send it together with `location_latitude` and `location_longitude`; `null` all three to clear the location. Detail view only."
                    },
                    "location_address": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Formatted address of the location, when the place has one. Detail view only."
                    },
                    "location_latitude": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "decimal",
                        "pattern": "^-?\\d+(\\.\\d{1,7})?$",
                        "x-plnnrly-minimum": -90,
                        "x-plnnrly-maximum": 90,
                        "description": "Latitude of the location in decimal degrees, -90 to 90. Detail view only."
                    },
                    "location_longitude": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "decimal",
                        "pattern": "^-?\\d+(\\.\\d{1,7})?$",
                        "x-plnnrly-minimum": -180,
                        "x-plnnrly-maximum": 180,
                        "description": "Longitude of the location in decimal degrees, -180 to 180. Detail view only."
                    },
                    "order": {
                        "type": "integer",
                        "description": "Position within the sprint or backlog, and the default sort. Not writable through PATCH: move an item with POST `work-items/{work_item}/actions/move` and `list: backlog`.",
                        "x-plnnrly-server-assigned": true
                    },
                    "column_order": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Position within the board column. Not writable through PATCH: move a card with `POST work-items/{work_item}/actions/move` and `list: board`.",
                        "x-plnnrly-server-assigned": true
                    },
                    "version": {
                        "type": "integer",
                        "description": "Revision counter, bumped once per write. It is the `ETag`'s revision: send it back as `If-Match` to detect a concurrent edit, which answers `412`."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "project_id": {
                        "type": "integer",
                        "description": "Project this work item belongs to. It cannot change: the issue key was allocated from the project's customer code."
                    },
                    "task_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Kanban task this work item is booked against, when it is booked against one. It must belong to the item's project. A sprint project books the running sprint's hours through it, so an item in that project's running sprint needs one; a simple-mode project does not. Setting it also makes the task's owner the assignee. Follow it to `/api/v2/tasks/{task}`."
                    },
                    "workflow_status_id": {
                        "type": "integer",
                        "description": "Current workflow status. Not writable through PATCH: moving between statuses is `POST work-items/{work_item}/actions/transition`, because the move is validated against the project workflow."
                    },
                    "work_type_id": {
                        "type": "integer",
                        "description": "Work type of this item."
                    },
                    "sprint_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Sprint the item sits in. Null means it is in the backlog."
                    },
                    "parent_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Parent work item, for a sub-issue. Null on a top-level item."
                    },
                    "assignee_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "User the work is assigned to. Null on an unassigned item. On a sprint project, setting it also moves the item to that user's only open task in the same budget, or in the project when the item has no task yet; with several or no candidates the task is left as it is."
                    },
                    "reporter_id": {
                        "type": "integer",
                        "description": "User who filed the work item. Always the authenticated user at create time; there is no way to file on somebody else's behalf.",
                        "x-plnnrly-server-assigned": true
                    },
                    "web_url": {
                        "type": "string",
                        "description": "Absolute URL that opens this work item in plnnrly, on the organisation's own hostname. It is a stable link, safe to store: it redirects to the first of the project's backlog, all-work list or board the signed-in user may open, and opens the item there; it lands on the project page when none of them applies. Read-only."
                    },
                    "workflow_status": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/WorkflowStatusSummary"
                            }
                        ],
                        "description": "The workflow status this item currently sits in. Always present."
                    },
                    "parent": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/WorkItemSummary"
                            },
                            {
                                "type": "null"
                            }
                        ],
                        "description": "The parent work item, on a sub-issue."
                    },
                    "assignee": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/UserSummary"
                            },
                            {
                                "type": "null"
                            }
                        ],
                        "description": "The user the work is assigned to. Expanding it additionally requires the users prefix's read grant and a token scope covering it."
                    },
                    "reporter": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/UserSummary"
                            }
                        ],
                        "description": "The user who filed the work item. Expanding it additionally requires the users prefix's read grant and a token scope covering it."
                    },
                    "work_type": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/WorkTypeSummary"
                            }
                        ],
                        "description": "The work type of this item. Expanding it additionally requires the `work_types` prefix's read grant and a token scope covering it."
                    },
                    "sprint": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/SprintSummary"
                            },
                            {
                                "type": "null"
                            }
                        ],
                        "description": "The sprint this item sits in, when it is not in the backlog."
                    },
                    "labels": {
                        "$ref": "#/components/schemas/LabelSummaryInclude",
                        "description": "Labels on this work item. `label_ids` takes at most 50 ids and replaces the whole set: a label you leave out is removed. Build it from `links.labels`, which lists every label while the Sprints feature is enabled, not from `include=labels`, which stops at 25.",
                        "x-plnnrly-include-only": true
                    },
                    "drive_items": {
                        "$ref": "#/components/schemas/DriveItemSummaryInclude",
                        "description": "Drive documents linked to this work item that you may open. `drive_item_ids` takes at most 25 ids and replaces the whole set: a document you leave out is unlinked, and so is every linked document you cannot open. Build it from `links.drive_items`, not from `include=drive_items`, which stops at 25. Expanding it additionally requires the `project_drive` prefix's access grant and a token scope covering it.",
                        "x-plnnrly-include-only": true
                    },
                    "comments": {
                        "$ref": "#/components/schemas/CommentSummaryInclude",
                        "description": "Comments on this work item, newest first, capped at 25. Follow `links.comments` for the rest.",
                        "x-plnnrly-include-only": true
                    },
                    "subtasks": {
                        "$ref": "#/components/schemas/SubtaskSummaryInclude",
                        "description": "Checklist subtasks on this work item, capped at 25. Follow `links.subtasks` for the rest.",
                        "x-plnnrly-include-only": true
                    },
                    "comments_count": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Number of comments on this work item."
                    },
                    "subtasks_count": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Number of checklist subtasks on this work item."
                    },
                    "attachments_count": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Number of files attached to this work item."
                    },
                    "child_work_items_count": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Number of child work items (sub-issues)."
                    },
                    "labels_count": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Number of labels on this work item."
                    },
                    "drive_items_count": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Number of linked drive documents the caller may open. Archived documents are not counted."
                    },
                    "links_count": {
                        "type": "integer",
                        "description": "Number of work items linked to this one, in either direction."
                    },
                    "links": {
                        "$ref": "#/components/schemas/WorkItemLinks"
                    }
                },
                "required": [
                    "id",
                    "issue_key",
                    "title",
                    "description",
                    "priority",
                    "due_date",
                    "estimation",
                    "remaining_estimate",
                    "location_name",
                    "location_address",
                    "location_latitude",
                    "location_longitude",
                    "order",
                    "column_order",
                    "version",
                    "created_at",
                    "updated_at",
                    "project_id",
                    "task_id",
                    "workflow_status_id",
                    "work_type_id",
                    "sprint_id",
                    "parent_id",
                    "assignee_id",
                    "reporter_id",
                    "web_url",
                    "workflow_status",
                    "parent",
                    "assignee",
                    "reporter",
                    "work_type",
                    "sprint",
                    "comments_count",
                    "subtasks_count",
                    "attachments_count",
                    "child_work_items_count",
                    "labels_count",
                    "drive_items_count",
                    "links_count",
                    "links"
                ],
                "description": "A work item: the unit of work a project's backlog and board are made of, addressed by numeric id or by its human-readable issue key (ACM-123) on the same path. It carries a title, a rich-text description, an estimate, a priority and a position in its project's workflow, and it is the parent of comments, checklist subtasks, attachments, links to other work items and its own child work items. Moving it to another workflow status is not a PATCH: the move is validated against the project workflow, so it lives at POST `work-items/{work_item}/actions/transition`."
            },
            "WorkItemLink": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "source_work_item_id": {
                        "type": "integer",
                        "description": "Work item the link points away from. On a link created through `POST work-items/{work_item}/links` this is always the work item named in the path."
                    },
                    "target_work_item_id": {
                        "type": "integer",
                        "description": "Work item the link points at. Always in the same project as the source."
                    },
                    "link_type": {
                        "type": "string",
                        "enum": [
                            "blocks",
                            "clones",
                            "duplicates",
                            "merged_into",
                            "causes",
                            "relates_to",
                            "split_to"
                        ],
                        "x-extensible-enum": true,
                        "description": "The relationship, stored from the source item's point of view: `blocks`, `clones`, `duplicates`, `merged_into`, `causes`, `relates_to` or `split_to`. Only `relates_to` is symmetric."
                    },
                    "created_by": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "User who created the link, or `null` where that account has since been deleted. Resolve it with `GET /api/v2/users/{user}`; it is not includable.",
                        "x-plnnrly-server-assigned": true
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "direction": {
                        "type": "string",
                        "description": "Which way this link points relative to the work item you asked about: `forward` when that item is the source, `inverse` when it is the target. On the flat `GET work-item-links/{work_item_link}` there is no work item in the path, so the link is read on its own terms and this is always `forward`."
                    },
                    "relationship": {
                        "type": "string",
                        "description": "The relationship in words, from the point of view of the work item you asked about: `blocks` from the source end, `is blocked by` from the target end. Always in English: the API pins the locale."
                    },
                    "source_work_item": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/WorkItemSummary"
                            }
                        ],
                        "description": "Work item the link points away from."
                    },
                    "target_work_item": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/WorkItemSummary"
                            }
                        ],
                        "description": "Work item the link points at."
                    },
                    "links": {
                        "$ref": "#/components/schemas/WorkItemLinkLinks"
                    }
                },
                "required": [
                    "id",
                    "source_work_item_id",
                    "target_work_item_id",
                    "link_type",
                    "created_by",
                    "created_at",
                    "updated_at",
                    "direction",
                    "relationship",
                    "source_work_item",
                    "target_work_item",
                    "links"
                ],
                "description": "A directed link between two work items (`blocks`, `duplicates`, `relates_to` and four more), the rows the linked items section in plnnrly is built from. A link is read relative to the work item you asked about: one row is \"blocks ACM-9\" from one end and \"is blocked by ACM-4\" from the other, which is what `direction` and `relationship` carry. There is no PATCH: the only mutable property of a link is which two items it joins, so re-linking is a DELETE plus a POST. Deleting either work item deletes the link with it."
            },
            "WorkItemLinkLinks": {
                "type": "object",
                "properties": {
                    "self": {
                        "type": "string",
                        "format": "uri",
                        "description": "The canonical URI of this record."
                    }
                },
                "required": [
                    "self"
                ],
                "additionalProperties": false
            },
            "WorkItemLinkList": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "source_work_item_id": {
                        "type": "integer",
                        "description": "Work item the link points away from. On a link created through `POST work-items/{work_item}/links` this is always the work item named in the path."
                    },
                    "target_work_item_id": {
                        "type": "integer",
                        "description": "Work item the link points at. Always in the same project as the source."
                    },
                    "link_type": {
                        "type": "string",
                        "enum": [
                            "blocks",
                            "clones",
                            "duplicates",
                            "merged_into",
                            "causes",
                            "relates_to",
                            "split_to"
                        ],
                        "x-extensible-enum": true,
                        "description": "The relationship, stored from the source item's point of view: `blocks`, `clones`, `duplicates`, `merged_into`, `causes`, `relates_to` or `split_to`. Only `relates_to` is symmetric."
                    },
                    "created_by": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "User who created the link, or `null` where that account has since been deleted. Resolve it with `GET /api/v2/users/{user}`; it is not includable.",
                        "x-plnnrly-server-assigned": true
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "direction": {
                        "type": "string",
                        "description": "Which way this link points relative to the work item you asked about: `forward` when that item is the source, `inverse` when it is the target. On the flat `GET work-item-links/{work_item_link}` there is no work item in the path, so the link is read on its own terms and this is always `forward`."
                    },
                    "relationship": {
                        "type": "string",
                        "description": "The relationship in words, from the point of view of the work item you asked about: `blocks` from the source end, `is blocked by` from the target end. Always in English: the API pins the locale."
                    },
                    "source_work_item": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/WorkItemSummary"
                            }
                        ],
                        "description": "Work item the link points away from."
                    },
                    "target_work_item": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/WorkItemSummary"
                            }
                        ],
                        "description": "Work item the link points at."
                    },
                    "links": {
                        "$ref": "#/components/schemas/WorkItemLinkLinks"
                    }
                },
                "required": [
                    "id",
                    "source_work_item_id",
                    "target_work_item_id",
                    "link_type",
                    "created_by",
                    "created_at",
                    "updated_at",
                    "direction",
                    "relationship",
                    "source_work_item",
                    "target_work_item",
                    "links"
                ],
                "description": "A directed link between two work items (`blocks`, `duplicates`, `relates_to` and four more), the rows the linked items section in plnnrly is built from. A link is read relative to the work item you asked about: one row is \"blocks ACM-9\" from one end and \"is blocked by ACM-4\" from the other, which is what `direction` and `relationship` carry. There is no PATCH: the only mutable property of a link is which two items it joins, so re-linking is a DELETE plus a POST. Deleting either work item deletes the link with it."
            },
            "WorkItemLinkSummary": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "source_work_item_id": {
                        "type": "integer",
                        "description": "Work item the link points away from."
                    },
                    "target_work_item_id": {
                        "type": "integer",
                        "description": "Work item the link points at."
                    }
                },
                "required": [
                    "id",
                    "source_work_item_id",
                    "target_work_item_id"
                ],
                "description": "A link between two work items, as referenced from another resource."
            },
            "WorkItemLinks": {
                "type": "object",
                "properties": {
                    "self": {
                        "type": "string",
                        "format": "uri",
                        "description": "The canonical URI of this record."
                    },
                    "comments": {
                        "type": "string",
                        "format": "uri",
                        "description": "Comments on this work item."
                    },
                    "subtasks": {
                        "type": "string",
                        "format": "uri",
                        "description": "Checklist subtasks on this work item."
                    },
                    "links": {
                        "type": "string",
                        "format": "uri",
                        "description": "Links between this work item and others, in either direction."
                    },
                    "attachments": {
                        "type": "string",
                        "format": "uri",
                        "description": "Files attached to this work item."
                    },
                    "child_work_items": {
                        "type": "string",
                        "format": "uri",
                        "description": "Child work items (sub-issues) of this work item."
                    },
                    "labels": {
                        "type": "string",
                        "format": "uri",
                        "description": "Every label on this work item, paginated. Present only while the Sprints feature is enabled, as `GET /labels` is."
                    },
                    "drive_items": {
                        "type": "string",
                        "format": "uri",
                        "description": "Every drive document linked to this work item that you may open, paginated."
                    }
                },
                "required": [
                    "self",
                    "comments",
                    "subtasks",
                    "links",
                    "attachments",
                    "child_work_items",
                    "drive_items"
                ],
                "additionalProperties": false
            },
            "WorkItemList": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "issue_key": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Human-readable key, e.g. `ACM-123`, allocated by the server from the project's customer code and a per-customer sequence. It also works in the path: GET `work-items/ACM-123` works. This is one of the two fields `?q=` searches.",
                        "x-plnnrly-server-assigned": true
                    },
                    "title": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Work item title. The other field `?q=` searches."
                    },
                    "priority": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "enum": [
                            "highest",
                            "high",
                            "normal",
                            "low",
                            "lowest",
                            null
                        ],
                        "x-extensible-enum": true,
                        "description": "Relative priority. Defaults to normal on create; `null` only on a legacy item that was never prioritised."
                    },
                    "due_date": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date",
                        "description": "Due date, a zone-free calendar date. Null when no date was set."
                    },
                    "estimation": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Original estimate, in minutes."
                    },
                    "remaining_estimate": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Remaining estimate, in minutes. Null where none was entered, in which case plnnrly derives one from the estimate and the logged time."
                    },
                    "order": {
                        "type": "integer",
                        "description": "Position within the sprint or backlog, and the default sort. Not writable through PATCH: move an item with POST `work-items/{work_item}/actions/move` and `list: backlog`.",
                        "x-plnnrly-server-assigned": true
                    },
                    "column_order": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Position within the board column. Not writable through PATCH: move a card with `POST work-items/{work_item}/actions/move` and `list: board`.",
                        "x-plnnrly-server-assigned": true
                    },
                    "version": {
                        "type": "integer",
                        "description": "Revision counter, bumped once per write. It is the `ETag`'s revision: send it back as `If-Match` to detect a concurrent edit, which answers `412`."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "project_id": {
                        "type": "integer",
                        "description": "Project this work item belongs to. It cannot change: the issue key was allocated from the project's customer code."
                    },
                    "task_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Kanban task this work item is booked against, when it is booked against one. It must belong to the item's project. A sprint project books the running sprint's hours through it, so an item in that project's running sprint needs one; a simple-mode project does not. Setting it also makes the task's owner the assignee. Follow it to `/api/v2/tasks/{task}`."
                    },
                    "workflow_status_id": {
                        "type": "integer",
                        "description": "Current workflow status. Not writable through PATCH: moving between statuses is `POST work-items/{work_item}/actions/transition`, because the move is validated against the project workflow."
                    },
                    "work_type_id": {
                        "type": "integer",
                        "description": "Work type of this item."
                    },
                    "sprint_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Sprint the item sits in. Null means it is in the backlog."
                    },
                    "parent_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Parent work item, for a sub-issue. Null on a top-level item."
                    },
                    "assignee_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "User the work is assigned to. Null on an unassigned item. On a sprint project, setting it also moves the item to that user's only open task in the same budget, or in the project when the item has no task yet; with several or no candidates the task is left as it is."
                    },
                    "reporter_id": {
                        "type": "integer",
                        "description": "User who filed the work item. Always the authenticated user at create time; there is no way to file on somebody else's behalf.",
                        "x-plnnrly-server-assigned": true
                    },
                    "web_url": {
                        "type": "string",
                        "description": "Absolute URL that opens this work item in plnnrly, on the organisation's own hostname. It is a stable link, safe to store: it redirects to the first of the project's backlog, all-work list or board the signed-in user may open, and opens the item there; it lands on the project page when none of them applies. Read-only."
                    },
                    "workflow_status": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/WorkflowStatusSummary"
                            }
                        ],
                        "description": "The workflow status this item currently sits in. Always present."
                    },
                    "parent": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/WorkItemSummary"
                            },
                            {
                                "type": "null"
                            }
                        ],
                        "description": "The parent work item, on a sub-issue."
                    },
                    "assignee": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/UserSummary"
                            },
                            {
                                "type": "null"
                            }
                        ],
                        "description": "The user the work is assigned to. Expanding it additionally requires the users prefix's read grant and a token scope covering it."
                    },
                    "reporter": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/UserSummary"
                            }
                        ],
                        "description": "The user who filed the work item. Expanding it additionally requires the users prefix's read grant and a token scope covering it."
                    },
                    "work_type": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/WorkTypeSummary"
                            }
                        ],
                        "description": "The work type of this item. Expanding it additionally requires the `work_types` prefix's read grant and a token scope covering it."
                    },
                    "sprint": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/SprintSummary"
                            },
                            {
                                "type": "null"
                            }
                        ],
                        "description": "The sprint this item sits in, when it is not in the backlog."
                    },
                    "labels": {
                        "$ref": "#/components/schemas/LabelSummaryInclude",
                        "description": "Labels on this work item. `label_ids` takes at most 50 ids and replaces the whole set: a label you leave out is removed. Build it from `links.labels`, which lists every label while the Sprints feature is enabled, not from `include=labels`, which stops at 25.",
                        "x-plnnrly-include-only": true
                    },
                    "drive_items": {
                        "$ref": "#/components/schemas/DriveItemSummaryInclude",
                        "description": "Drive documents linked to this work item that you may open. `drive_item_ids` takes at most 25 ids and replaces the whole set: a document you leave out is unlinked, and so is every linked document you cannot open. Build it from `links.drive_items`, not from `include=drive_items`, which stops at 25. Expanding it additionally requires the `project_drive` prefix's access grant and a token scope covering it.",
                        "x-plnnrly-include-only": true
                    },
                    "comments": {
                        "$ref": "#/components/schemas/CommentSummaryInclude",
                        "description": "Comments on this work item, newest first, capped at 25. Follow `links.comments` for the rest.",
                        "x-plnnrly-include-only": true
                    },
                    "subtasks": {
                        "$ref": "#/components/schemas/SubtaskSummaryInclude",
                        "description": "Checklist subtasks on this work item, capped at 25. Follow `links.subtasks` for the rest.",
                        "x-plnnrly-include-only": true
                    },
                    "comments_count": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Number of comments on this work item."
                    },
                    "subtasks_count": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Number of checklist subtasks on this work item."
                    },
                    "attachments_count": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Number of files attached to this work item."
                    },
                    "child_work_items_count": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Number of child work items (sub-issues)."
                    },
                    "labels_count": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Number of labels on this work item."
                    },
                    "drive_items_count": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Number of linked drive documents the caller may open. Archived documents are not counted."
                    },
                    "links_count": {
                        "type": "integer",
                        "description": "Number of work items linked to this one, in either direction."
                    },
                    "links": {
                        "$ref": "#/components/schemas/WorkItemLinks"
                    }
                },
                "required": [
                    "id",
                    "issue_key",
                    "title",
                    "priority",
                    "due_date",
                    "estimation",
                    "remaining_estimate",
                    "order",
                    "column_order",
                    "version",
                    "created_at",
                    "updated_at",
                    "project_id",
                    "task_id",
                    "workflow_status_id",
                    "work_type_id",
                    "sprint_id",
                    "parent_id",
                    "assignee_id",
                    "reporter_id",
                    "web_url",
                    "workflow_status",
                    "parent",
                    "assignee",
                    "reporter",
                    "work_type",
                    "sprint",
                    "comments_count",
                    "subtasks_count",
                    "attachments_count",
                    "child_work_items_count",
                    "labels_count",
                    "drive_items_count",
                    "links_count",
                    "links"
                ],
                "description": "A work item: the unit of work a project's backlog and board are made of, addressed by numeric id or by its human-readable issue key (ACM-123) on the same path. It carries a title, a rich-text description, an estimate, a priority and a position in its project's workflow, and it is the parent of comments, checklist subtasks, attachments, links to other work items and its own child work items. Moving it to another workflow status is not a PATCH: the move is validated against the project workflow, so it lives at POST `work-items/{work_item}/actions/transition`."
            },
            "WorkItemSummary": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "issue_key": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255,
                        "description": "Human-readable key, e.g. `ACM-123`."
                    },
                    "title": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Work item title."
                    }
                },
                "required": [
                    "id",
                    "issue_key",
                    "title"
                ],
                "description": "A work item, as referenced from another resource."
            },
            "WorkItemsAttachmentsIndexMeta": {
                "type": "object",
                "properties": {
                    "page": {
                        "$ref": "#/components/schemas/Page"
                    },
                    "warnings": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "code": {
                                    "type": "string",
                                    "enum": [
                                        "signed_url_unavailable"
                                    ],
                                    "x-extensible-enum": true,
                                    "description": "Machine-readable warning code. Switch on this, never on detail."
                                },
                                "detail": {
                                    "type": "string",
                                    "description": "Human-readable explanation."
                                },
                                "pointer": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "description": "RFC 6901 pointer into the request body, where the warning is about a body field."
                                },
                                "parameter": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "description": "The query-parameter name, brackets included, where the warning is about a parameter."
                                },
                                "sunset": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "format": "date",
                                    "description": "Date the deprecated thing stops working."
                                },
                                "replacement": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "description": "What to use instead."
                                }
                            },
                            "required": [
                                "code",
                                "detail"
                            ],
                            "description": "An advisory about this request. It never changes the status code or `data`."
                        },
                        "description": "Non-fatal notes about this request. Switch on `code`, never on `detail`."
                    }
                },
                "additionalProperties": false,
                "required": [
                    "page"
                ]
            },
            "WorkItemsAttachmentsStoreMeta": {
                "type": "object",
                "properties": {
                    "warnings": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "code": {
                                    "type": "string",
                                    "enum": [
                                        "signed_url_unavailable"
                                    ],
                                    "x-extensible-enum": true,
                                    "description": "Machine-readable warning code. Switch on this, never on detail."
                                },
                                "detail": {
                                    "type": "string",
                                    "description": "Human-readable explanation."
                                },
                                "pointer": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "description": "RFC 6901 pointer into the request body, where the warning is about a body field."
                                },
                                "parameter": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "description": "The query-parameter name, brackets included, where the warning is about a parameter."
                                },
                                "sunset": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "format": "date",
                                    "description": "Date the deprecated thing stops working."
                                },
                                "replacement": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "description": "What to use instead."
                                }
                            },
                            "required": [
                                "code",
                                "detail"
                            ],
                            "description": "An advisory about this request. It never changes the status code or `data`."
                        },
                        "description": "Non-fatal notes about this request. Switch on `code`, never on `detail`."
                    }
                },
                "additionalProperties": false
            },
            "WorkItemsCommentsIndexMeta": {
                "type": "object",
                "properties": {
                    "page": {
                        "$ref": "#/components/schemas/PageOptionalTotal"
                    }
                },
                "additionalProperties": false,
                "required": [
                    "page"
                ]
            },
            "WorkItemsIndexMeta": {
                "type": "object",
                "properties": {
                    "page": {
                        "$ref": "#/components/schemas/PageOptionalTotal"
                    },
                    "warnings": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "code": {
                                    "type": "string",
                                    "enum": [
                                        "include_truncated"
                                    ],
                                    "x-extensible-enum": true,
                                    "description": "Machine-readable warning code. Switch on this, never on detail."
                                },
                                "detail": {
                                    "type": "string",
                                    "description": "Human-readable explanation."
                                },
                                "pointer": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "description": "RFC 6901 pointer into the request body, where the warning is about a body field."
                                },
                                "parameter": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "description": "The query-parameter name, brackets included, where the warning is about a parameter."
                                },
                                "sunset": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "format": "date",
                                    "description": "Date the deprecated thing stops working."
                                },
                                "replacement": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "description": "What to use instead."
                                }
                            },
                            "required": [
                                "code",
                                "detail"
                            ],
                            "description": "An advisory about this request. It never changes the status code or `data`."
                        },
                        "description": "Non-fatal notes about this request. Switch on `code`, never on `detail`."
                    }
                },
                "additionalProperties": false,
                "required": [
                    "page"
                ]
            },
            "WorkItemsLinksIndexMeta": {
                "type": "object",
                "properties": {
                    "page": {
                        "$ref": "#/components/schemas/Page"
                    }
                },
                "additionalProperties": false,
                "required": [
                    "page"
                ]
            },
            "WorkItemsShowMeta": {
                "type": "object",
                "properties": {
                    "actions": {
                        "type": "array",
                        "items": {
                            "type": "string",
                            "enum": [
                                "move",
                                "transition"
                            ]
                        },
                        "description": "The actions the caller may currently invoke on this record."
                    }
                },
                "additionalProperties": false
            },
            "WorkItemsSubtasksIndexMeta": {
                "type": "object",
                "properties": {
                    "page": {
                        "$ref": "#/components/schemas/Page"
                    }
                },
                "additionalProperties": false,
                "required": [
                    "page"
                ]
            },
            "WorkType": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Work-type name as shown in the UI, e.g. `Bug`."
                    },
                    "color": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Six-digit hex colour, e.g. `#6b7280`. A stored value that is not six-digit hex reads back as `#6b7280`, so writing this field back unchanged replaces the malformed original."
                    },
                    "icon": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Icon name, e.g. `heroicon-o-rectangle-stack`: not a URL and not markup."
                    },
                    "order": {
                        "type": "integer",
                        "description": "Display position; set by dragging in plnnrly and not writable. API-created types are created at 0 and fall back to the id tiebreaker.",
                        "x-plnnrly-server-assigned": true
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "links": {
                        "$ref": "#/components/schemas/WorkTypeLinks"
                    }
                },
                "required": [
                    "id",
                    "name",
                    "color",
                    "icon",
                    "order",
                    "created_at",
                    "updated_at",
                    "links"
                ],
                "description": "A work-item type: the organisation-wide categories work items are filed under (bug, task, story, …), carrying the colour and icon the backlog, board and Gantt render beside them. A type still used by work items cannot be deleted."
            },
            "WorkTypeLinks": {
                "type": "object",
                "properties": {
                    "self": {
                        "type": "string",
                        "format": "uri",
                        "description": "The canonical URI of this record."
                    }
                },
                "required": [
                    "self"
                ],
                "additionalProperties": false
            },
            "WorkTypeList": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Work-type name as shown in the UI, e.g. `Bug`."
                    },
                    "color": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Six-digit hex colour, e.g. `#6b7280`. A stored value that is not six-digit hex reads back as `#6b7280`, so writing this field back unchanged replaces the malformed original."
                    },
                    "icon": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Icon name, e.g. `heroicon-o-rectangle-stack`: not a URL and not markup."
                    },
                    "order": {
                        "type": "integer",
                        "description": "Display position; set by dragging in plnnrly and not writable. API-created types are created at 0 and fall back to the id tiebreaker.",
                        "x-plnnrly-server-assigned": true
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "links": {
                        "$ref": "#/components/schemas/WorkTypeLinks"
                    }
                },
                "required": [
                    "id",
                    "name",
                    "color",
                    "icon",
                    "order",
                    "created_at",
                    "updated_at",
                    "links"
                ],
                "description": "A work-item type: the organisation-wide categories work items are filed under (bug, task, story, …), carrying the colour and icon the backlog, board and Gantt render beside them. A type still used by work items cannot be deleted."
            },
            "WorkTypeSummary": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Work-type name as shown in the UI."
                    },
                    "color": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Six-digit hex colour, e.g. `#6b7280`."
                    },
                    "icon": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Icon name, e.g. `heroicon-o-rectangle-stack`."
                    }
                },
                "required": [
                    "id",
                    "name",
                    "color",
                    "icon"
                ],
                "description": "A work-item type, as referenced from another resource."
            },
            "WorkTypesIndexMeta": {
                "type": "object",
                "properties": {
                    "page": {
                        "$ref": "#/components/schemas/Page"
                    }
                },
                "additionalProperties": false,
                "required": [
                    "page"
                ]
            },
            "Workflow": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Workflow name as shown in the UI, e.g. `Standard`."
                    },
                    "order": {
                        "type": "integer",
                        "description": "Display position; set by dragging in plnnrly and not writable. API-created workflows are created at 0 and fall back to the id tiebreaker.",
                        "x-plnnrly-server-assigned": true
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "statuses_count": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Number of statuses in this workflow."
                    },
                    "links": {
                        "$ref": "#/components/schemas/WorkflowLinks"
                    }
                },
                "required": [
                    "id",
                    "name",
                    "order",
                    "created_at",
                    "updated_at",
                    "statuses_count",
                    "links"
                ],
                "description": "A workflow: the named set of statuses a work item moves through, plus the transitions allowed between them. A project points at one workflow. Statuses and transitions are managed as their own collections: a workflow is created empty."
            },
            "WorkflowLinks": {
                "type": "object",
                "properties": {
                    "self": {
                        "type": "string",
                        "format": "uri",
                        "description": "The canonical URI of this record."
                    },
                    "statuses": {
                        "type": "string",
                        "format": "uri",
                        "description": "Statuses in this workflow, in workflow order."
                    }
                },
                "required": [
                    "self",
                    "statuses"
                ],
                "additionalProperties": false
            },
            "WorkflowList": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Workflow name as shown in the UI, e.g. `Standard`."
                    },
                    "order": {
                        "type": "integer",
                        "description": "Display position; set by dragging in plnnrly and not writable. API-created workflows are created at 0 and fall back to the id tiebreaker.",
                        "x-plnnrly-server-assigned": true
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "statuses_count": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Number of statuses in this workflow."
                    },
                    "links": {
                        "$ref": "#/components/schemas/WorkflowLinks"
                    }
                },
                "required": [
                    "id",
                    "name",
                    "order",
                    "created_at",
                    "updated_at",
                    "statuses_count",
                    "links"
                ],
                "description": "A workflow: the named set of statuses a work item moves through, plus the transitions allowed between them. A project points at one workflow. Statuses and transitions are managed as their own collections: a workflow is created empty."
            },
            "WorkflowStatus": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "workflow_id": {
                        "type": "integer",
                        "description": "Workflow this status belongs to. A status cannot be moved to another workflow: its transitions and the work items sitting in it belong to this one."
                    },
                    "name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Status name as shown on the board column and the status badge, e.g. `To do`."
                    },
                    "color": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Six-digit hex colour, e.g. `#6b7280`. A stored value that is not six-digit hex reads back as `#6b7280`, so writing this field back unchanged replaces the malformed original."
                    },
                    "is_resolution": {
                        "type": "boolean",
                        "description": "Whether a work item reaching this status counts as resolved. Drives the backlog's done state and what a completed sprint carries over."
                    },
                    "order": {
                        "type": "integer",
                        "description": "Position within the workflow, left to right on the board; set by dragging in plnnrly and not writable. API-created statuses are created at 0 and fall back to the id tiebreaker.",
                        "x-plnnrly-server-assigned": true
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "workflow": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/WorkflowSummary"
                            }
                        ],
                        "description": "Workflow this status belongs to."
                    },
                    "links": {
                        "$ref": "#/components/schemas/WorkflowStatusLinks"
                    }
                },
                "required": [
                    "id",
                    "workflow_id",
                    "name",
                    "color",
                    "is_resolution",
                    "order",
                    "created_at",
                    "updated_at",
                    "workflow",
                    "links"
                ],
                "description": "One status in a workflow: the column a work item sits in, with the colour the badge is drawn in and whether reaching it resolves the item. Statuses belong to exactly one workflow and cannot be moved between workflows. Which statuses a work item may move to is a separate collection: the workflow transitions."
            },
            "WorkflowStatusLinks": {
                "type": "object",
                "properties": {
                    "self": {
                        "type": "string",
                        "format": "uri",
                        "description": "The canonical URI of this record."
                    }
                },
                "required": [
                    "self"
                ],
                "additionalProperties": false
            },
            "WorkflowStatusList": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "workflow_id": {
                        "type": "integer",
                        "description": "Workflow this status belongs to. A status cannot be moved to another workflow: its transitions and the work items sitting in it belong to this one."
                    },
                    "name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Status name as shown on the board column and the status badge, e.g. `To do`."
                    },
                    "color": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Six-digit hex colour, e.g. `#6b7280`. A stored value that is not six-digit hex reads back as `#6b7280`, so writing this field back unchanged replaces the malformed original."
                    },
                    "is_resolution": {
                        "type": "boolean",
                        "description": "Whether a work item reaching this status counts as resolved. Drives the backlog's done state and what a completed sprint carries over."
                    },
                    "order": {
                        "type": "integer",
                        "description": "Position within the workflow, left to right on the board; set by dragging in plnnrly and not writable. API-created statuses are created at 0 and fall back to the id tiebreaker.",
                        "x-plnnrly-server-assigned": true
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "workflow": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/WorkflowSummary"
                            }
                        ],
                        "description": "Workflow this status belongs to."
                    },
                    "links": {
                        "$ref": "#/components/schemas/WorkflowStatusLinks"
                    }
                },
                "required": [
                    "id",
                    "workflow_id",
                    "name",
                    "color",
                    "is_resolution",
                    "order",
                    "created_at",
                    "updated_at",
                    "workflow",
                    "links"
                ],
                "description": "One status in a workflow: the column a work item sits in, with the colour the badge is drawn in and whether reaching it resolves the item. Statuses belong to exactly one workflow and cannot be moved between workflows. Which statuses a work item may move to is a separate collection: the workflow transitions."
            },
            "WorkflowStatusSummary": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Status name as shown in the UI."
                    },
                    "color": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Six-digit hex colour, e.g. `#6b7280`."
                    },
                    "is_resolution": {
                        "type": "boolean",
                        "description": "Whether reaching this status resolves the work item."
                    }
                },
                "required": [
                    "id",
                    "name",
                    "color",
                    "is_resolution"
                ],
                "description": "A workflow status, as referenced from another resource."
            },
            "WorkflowStatusesIndexMeta": {
                "type": "object",
                "properties": {
                    "page": {
                        "$ref": "#/components/schemas/Page"
                    }
                },
                "additionalProperties": false,
                "required": [
                    "page"
                ]
            },
            "WorkflowSummary": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Workflow name as shown in the UI."
                    }
                },
                "required": [
                    "id",
                    "name"
                ],
                "description": "A workflow, as referenced from another resource."
            },
            "WorkflowTransition": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "workflow_id": {
                        "type": "integer",
                        "description": "Workflow this edge belongs to. Both statuses belong to the same workflow, and the edge cannot be moved to another one."
                    },
                    "from_status_id": {
                        "type": "integer",
                        "description": "Status a work item must currently be in for this edge to apply."
                    },
                    "to_status_id": {
                        "type": "integer",
                        "description": "Status the work item moves to. Always different from `from_status_id`."
                    },
                    "name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Transition label as shown on the status button, e.g. `Start progress`."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "workflow": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/WorkflowSummary"
                            }
                        ],
                        "description": "Workflow this edge belongs to."
                    },
                    "from_status": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/WorkflowStatusSummary"
                            }
                        ],
                        "description": "Status the edge leads out of."
                    },
                    "to_status": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/WorkflowStatusSummary"
                            }
                        ],
                        "description": "Status the edge leads into."
                    },
                    "links": {
                        "$ref": "#/components/schemas/WorkflowTransitionLinks"
                    }
                },
                "required": [
                    "id",
                    "workflow_id",
                    "from_status_id",
                    "to_status_id",
                    "name",
                    "created_at",
                    "updated_at",
                    "workflow",
                    "from_status",
                    "to_status",
                    "links"
                ],
                "description": "One legal edge of a workflow: the move a work item may make from one status to another, with the label the transition button carries. Every status change is checked against these edges, so an edge that does not exist here cannot be walked by the API or in plnnrly. Edges are directed, and duplicates are legal."
            },
            "WorkflowTransitionLinks": {
                "type": "object",
                "properties": {
                    "self": {
                        "type": "string",
                        "format": "uri",
                        "description": "The canonical URI of this record."
                    }
                },
                "required": [
                    "self"
                ],
                "additionalProperties": false
            },
            "WorkflowTransitionList": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "workflow_id": {
                        "type": "integer",
                        "description": "Workflow this edge belongs to. Both statuses belong to the same workflow, and the edge cannot be moved to another one."
                    },
                    "from_status_id": {
                        "type": "integer",
                        "description": "Status a work item must currently be in for this edge to apply."
                    },
                    "to_status_id": {
                        "type": "integer",
                        "description": "Status the work item moves to. Always different from `from_status_id`."
                    },
                    "name": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Transition label as shown on the status button, e.g. `Start progress`."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Creation timestamp."
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Last-write timestamp; use for incremental sync."
                    },
                    "workflow": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/WorkflowSummary"
                            }
                        ],
                        "description": "Workflow this edge belongs to."
                    },
                    "from_status": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/WorkflowStatusSummary"
                            }
                        ],
                        "description": "Status the edge leads out of."
                    },
                    "to_status": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/Ref"
                            },
                            {
                                "$ref": "#/components/schemas/WorkflowStatusSummary"
                            }
                        ],
                        "description": "Status the edge leads into."
                    },
                    "links": {
                        "$ref": "#/components/schemas/WorkflowTransitionLinks"
                    }
                },
                "required": [
                    "id",
                    "workflow_id",
                    "from_status_id",
                    "to_status_id",
                    "name",
                    "created_at",
                    "updated_at",
                    "workflow",
                    "from_status",
                    "to_status",
                    "links"
                ],
                "description": "One legal edge of a workflow: the move a work item may make from one status to another, with the label the transition button carries. Every status change is checked against these edges, so an edge that does not exist here cannot be walked by the API or in plnnrly. Edges are directed, and duplicates are legal."
            },
            "WorkflowTransitionSummary": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "Unique identifier."
                    },
                    "from_status_id": {
                        "type": "integer",
                        "description": "Status the edge leads out of."
                    },
                    "to_status_id": {
                        "type": "integer",
                        "description": "Status the edge leads into."
                    }
                },
                "required": [
                    "id",
                    "from_status_id",
                    "to_status_id"
                ],
                "description": "A workflow transition, as referenced from another resource."
            },
            "WorkflowTransitionsIndexMeta": {
                "type": "object",
                "properties": {
                    "page": {
                        "$ref": "#/components/schemas/Page"
                    }
                },
                "additionalProperties": false,
                "required": [
                    "page"
                ]
            },
            "WorkflowsIndexMeta": {
                "type": "object",
                "properties": {
                    "page": {
                        "$ref": "#/components/schemas/Page"
                    }
                },
                "additionalProperties": false,
                "required": [
                    "page"
                ]
            },
            "WorklogsIndexMeta": {
                "type": "object",
                "properties": {
                    "page": {
                        "$ref": "#/components/schemas/PageOptionalTotal"
                    }
                },
                "additionalProperties": false,
                "required": [
                    "page"
                ]
            },
            "WorklogsStoreMeta": {
                "type": "object",
                "properties": {
                    "warnings": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "code": {
                                    "type": "string",
                                    "enum": [
                                        "input_ignored"
                                    ],
                                    "x-extensible-enum": true,
                                    "description": "Machine-readable warning code. Switch on this, never on detail."
                                },
                                "detail": {
                                    "type": "string",
                                    "description": "Human-readable explanation."
                                },
                                "pointer": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "description": "RFC 6901 pointer into the request body, where the warning is about a body field."
                                },
                                "parameter": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "description": "The query-parameter name, brackets included, where the warning is about a parameter."
                                },
                                "sunset": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "format": "date",
                                    "description": "Date the deprecated thing stops working."
                                },
                                "replacement": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "description": "What to use instead."
                                }
                            },
                            "required": [
                                "code",
                                "detail"
                            ],
                            "description": "An advisory about this request. It never changes the status code or `data`."
                        },
                        "description": "Non-fatal notes about this request. Switch on `code`, never on `detail`."
                    }
                },
                "additionalProperties": false
            },
            "WorklogsUpdateMeta": {
                "type": "object",
                "properties": {
                    "warnings": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "code": {
                                    "type": "string",
                                    "enum": [
                                        "input_ignored"
                                    ],
                                    "x-extensible-enum": true,
                                    "description": "Machine-readable warning code. Switch on this, never on detail."
                                },
                                "detail": {
                                    "type": "string",
                                    "description": "Human-readable explanation."
                                },
                                "pointer": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "description": "RFC 6901 pointer into the request body, where the warning is about a body field."
                                },
                                "parameter": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "description": "The query-parameter name, brackets included, where the warning is about a parameter."
                                },
                                "sunset": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "format": "date",
                                    "description": "Date the deprecated thing stops working."
                                },
                                "replacement": {
                                    "type": [
                                        "string",
                                        "null"
                                    ],
                                    "description": "What to use instead."
                                }
                            },
                            "required": [
                                "code",
                                "detail"
                            ],
                            "description": "An advisory about this request. It never changes the status code or `data`."
                        },
                        "description": "Non-fatal notes about this request. Switch on `code`, never on `detail`."
                    }
                },
                "additionalProperties": false
            }
        },
        "securitySchemes": {
            "bearerToken": {
                "type": "http",
                "scheme": "bearer",
                "bearerFormat": "Personal access token or OAuth access token",
                "description": "A personal access token you create under API access in the user menu, or an OAuth access token. Its abilities are `api:{read|write|destroy}`, optionally narrowed to one resource prefix."
            },
            "oauth2": {
                "type": "oauth2",
                "description": "Authorization code with PKCE for native and public clients, with dynamic client registration at `/oauth/register`. Endpoints live on your plnnrly address. Scopes are the token abilities without `api:destroy`.",
                "flows": {
                    "authorizationCode": {
                        "authorizationUrl": "/oauth/authorize",
                        "tokenUrl": "/oauth/token",
                        "refreshUrl": "/oauth/token",
                        "scopes": {
                            "api:read": "Read every resource the user may access.",
                            "api:read:projects": "Read `projects` resources.",
                            "api:read:budgets": "Read `budgets` resources.",
                            "api:read:budget_groups": "Read `budget_groups` resources.",
                            "api:read:work_types": "Read `work_types` resources.",
                            "api:read:workflows": "Read `workflows` resources.",
                            "api:read:customers": "Read `customers` resources.",
                            "api:read:customer_categories": "Read `customer_categories` resources.",
                            "api:read:sales_invoices": "Read `sales_invoices` resources.",
                            "api:read:invoices": "Read `invoices` resources.",
                            "api:read:customer_subscriptions": "Read `customer_subscriptions` resources.",
                            "api:read:subscription_types": "Read `subscription_types` resources.",
                            "api:read:subscription_products": "Read `subscription_products` resources.",
                            "api:read:company_branches": "Read `company_branches` resources.",
                            "api:read:users": "Read `users` resources.",
                            "api:read:project_management": "Read `project_management` resources.",
                            "api:read:kanban": "Read `kanban` resources.",
                            "api:read:times": "Read `times` resources.",
                            "api:read:hours": "Read `hours` resources.",
                            "api:read:project_drive": "Read `project_drive` resources.",
                            "api:read:customer_drive": "Read `customer_drive` resources.",
                            "api:read:organization_drive": "Read `organization_drive` resources.",
                            "api:read:user_drive": "Read `user_drive` resources.",
                            "api:read:absences": "Read `absences` resources.",
                            "api:read:holidays": "Read `holidays` resources.",
                            "api:read:free_days": "Read `free_days` resources.",
                            "api:read:checkins": "Read `checkins` resources.",
                            "api:read:furlough": "Read `furlough` resources.",
                            "api:read:events": "Read `events` resources.",
                            "api:write": "Create and update every resource the user may access.",
                            "api:write:projects": "Create and update `projects` resources.",
                            "api:write:budgets": "Create and update `budgets` resources.",
                            "api:write:budget_groups": "Create and update `budget_groups` resources.",
                            "api:write:work_types": "Create and update `work_types` resources.",
                            "api:write:workflows": "Create and update `workflows` resources.",
                            "api:write:customers": "Create and update `customers` resources.",
                            "api:write:customer_categories": "Create and update `customer_categories` resources.",
                            "api:write:sales_invoices": "Create and update `sales_invoices` resources.",
                            "api:write:invoices": "Create and update `invoices` resources.",
                            "api:write:customer_subscriptions": "Create and update `customer_subscriptions` resources.",
                            "api:write:subscription_types": "Create and update `subscription_types` resources.",
                            "api:write:subscription_products": "Create and update `subscription_products` resources.",
                            "api:write:company_branches": "Create and update `company_branches` resources.",
                            "api:write:users": "Create and update `users` resources.",
                            "api:write:project_management": "Create and update `project_management` resources.",
                            "api:write:kanban": "Create and update `kanban` resources.",
                            "api:write:times": "Create and update `times` resources.",
                            "api:write:hours": "Create and update `hours` resources.",
                            "api:write:project_drive": "Create and update `project_drive` resources.",
                            "api:write:customer_drive": "Create and update `customer_drive` resources.",
                            "api:write:organization_drive": "Create and update `organization_drive` resources.",
                            "api:write:user_drive": "Create and update `user_drive` resources.",
                            "api:write:absences": "Create and update `absences` resources.",
                            "api:write:holidays": "Create and update `holidays` resources.",
                            "api:write:free_days": "Create and update `free_days` resources.",
                            "api:write:checkins": "Create and update `checkins` resources.",
                            "api:write:furlough": "Create and update `furlough` resources.",
                            "api:write:events": "Create and update `events` resources."
                        }
                    }
                }
            }
        }
    },
    "externalDocs": {
        "url": "https://developers.plnnrly.com/api/v2",
        "description": "Guides"
    }
}
